- Significant refactoring resulting in a 34% reduction in size of the codebase (excluding the Lightweight Charts package) and greater efficiency.

- Upgraded to Lightweight Charts v4.0.1.
- Added a ‘hover’ item to the returning dictionary from subscribe_click.
- Markers and SubCharts no longer use a UUID for identification, but an 8 character string.
This commit is contained in:
louisnw
2023-05-21 15:42:57 +01:00
parent 445d9b67d3
commit 7ea2b0ac19
10 changed files with 336 additions and 663 deletions

View File

@ -6,7 +6,7 @@
[![Documentation](https://img.shields.io/badge/documentation-006ee3)](https://lightweight-charts-python.readthedocs.io/en/latest/docs.html)
![cover](cover.png)
![cover](https://raw.githubusercontent.com/louisnw01/lightweight-charts-python/main/cover.png)
lightweight-charts-python aims to provide a simple and pythonic way to access and implement [TradingView's Lightweight Charts](https://www.tradingview.com/lightweight-charts/).
@ -42,7 +42,7 @@ if __name__ == '__main__':
chart.show(block=True)
```
![setting_data image](https://github.com/louisnw01/lightweight-charts-python/blob/main/examples/1_setting_data/setting_data.png)
![setting_data image](https://raw.githubusercontent.com/louisnw01/lightweight-charts-python/main/examples/1_setting_data/setting_data.png)
___
### 2. Updating bars in real-time:
@ -76,7 +76,7 @@ if __name__ == '__main__':
```
![live data gif](https://github.com/louisnw01/lightweight-charts-python/blob/main/examples/2_live_data/live_data.gif)
![live data gif](https://github.com/louisnw01/lightweight-charts-python/blob/main/examples/2_live_data/live_data.gif?raw=true)
___
### 3. Updating bars from tick data in real-time:
@ -106,7 +106,7 @@ if __name__ == '__main__':
sleep(0.3)
```
![tick data gif](https://github.com/louisnw01/lightweight-charts-python/blob/main/examples/3_tick_data/tick_data.gif)
![tick data gif](https://raw.githubusercontent.com/louisnw01/lightweight-charts-python/main/examples/3_tick_data/tick_data.gif)
___
### 4. Line Indicators:
@ -140,7 +140,7 @@ if __name__ == '__main__':
chart.show(block=True)
```
![line indicators image](https://github.com/louisnw01/lightweight-charts-python/blob/main/examples/4_line_indicators/line_indicators.png)
![line indicators image](https://raw.githubusercontent.com/louisnw01/lightweight-charts-python/main/examples/4_line_indicators/line_indicators.png)
___
### 5. Styling:
@ -177,7 +177,7 @@ if __name__ == '__main__':
chart.show(block=True)
```
![styling image](https://github.com/louisnw01/lightweight-charts-python/blob/main/examples/5_styling/styling.png)
![styling image](https://raw.githubusercontent.com/louisnw01/lightweight-charts-python/main/examples/5_styling/styling.png)
___
### 6. Callbacks:
@ -203,7 +203,7 @@ if __name__ == '__main__':
chart.show(block=True)
```
![callbacks gif](https://github.com/louisnw01/lightweight-charts-python/blob/main/examples/6_callbacks/callbacks.gif)
![callbacks gif](https://raw.githubusercontent.com/louisnw01/lightweight-charts-python/main/examples/6_callbacks/callbacks.gif)
___
[![Documentation](https://img.shields.io/badge/documentation-006ee3)](https://lightweight-charts-python.readthedocs.io/en/latest/docs.html)
@ -213,4 +213,3 @@ ___
_This package is an independent creation and has not been endorsed, sponsored, or approved by TradingView. The author of this package does not have any official relationship with TradingView, and the package does not represent the views or opinions of TradingView._

View File

@ -1,7 +1,7 @@
project = 'lightweight-charts-python'
copyright = '2023, louisnw'
author = 'louisnw'
release = '1.0.3'
release = '1.0.7'
extensions = ["myst_parser"]

View File

@ -6,7 +6,7 @@
[![License](https://img.shields.io/github/license/louisnw01/lightweight-charts-python?color=9c2400)](https://github.com/louisnw01/lightweight-charts-python/blob/main/LICENSE)
[![Stars - lightweight-charts-python](https://img.shields.io/github/stars/louisnw01/lightweight-charts-python?style=social)](https://github.com/louisnw01/lightweight-charts-python)
[![Forks - lightweight-charts-python](https://img.shields.io/github/forks/louisnw01/lightweight-charts-python?style=social)](https://github.com/louisnw01/lightweight-charts-python)
___
## Common Methods
These methods can be used within the `Chart`, `SubChart`, `QtChart`, and `WxChart` objects.
@ -57,17 +57,17 @@ Creates and returns a [Line](#line) object.
___
### `marker`
`time: datetime` | `position: 'above'/'below'/'inside'` | `shape: 'arrow_up'/'arrow_down'/'circle'/'square'` | `color: str` | `text: str` | `-> UUID`
`time: datetime` | `position: 'above'/'below'/'inside'` | `shape: 'arrow_up'/'arrow_down'/'circle'/'square'` | `color: str` | `text: str` | `-> str`
Adds a marker to the chart, and returns its UUID.
Adds a marker to the chart, and returns its id.
If the `time` parameter is not given, the marker will be placed at the latest bar.
___
### `remove_marker`
`m_id: UUID`
`marker_id: str`
Removes the marker with the given UUID.
Removes the marker with the given id.
Usage:
```python
@ -162,13 +162,13 @@ ___
Subscribes the given function to a chart 'click' event.
The event emits a dictionary containing the bar at the time clicked, with the keys:
The event emits a dictionary containing the bar at the time clicked, the id of the `Chart` or `SubChart`, and the hover price:
`time | open | high | low | close`
`time | open | high | low | close | id | hover`
___
### `create_subchart`
`volume_enabled: bool` | `position: 'left'/'right'/'top'/'bottom'`, `width: float` | `height: float` | `sync: bool/UUID` | `-> SubChart`
`volume_enabled: bool` | `position: 'left'/'right'/'top'/'bottom'`, `width: float` | `height: float` | `sync: bool/str` | `-> SubChart`
Creates and returns a [SubChart](#subchart) object, placing it adjacent to the declaring `Chart` or `SubChart`.
@ -176,9 +176,7 @@ Creates and returns a [SubChart](#subchart) object, placing it adjacent to the d
`height` | `width`: Specifies the size of the `SubChart`, where `1` is the width/height of the window (100%)
`sync`: If given as `True`, the `SubChart`'s time scale will follow that of the declaring `Chart` or `SubChart`. If a `UUID` object is passed, the `SubChart` will follow the panel with the given `UUID`.
Chart `UUID`'s can be accessed from the`chart.id` and `subchart.id` attributes.
`sync`: If given as `True`, the `SubChart`'s time scale will follow that of the declaring `Chart` or `SubChart`. If a `str` is passed, the `SubChart` will follow the panel with the given id. Chart ids can be accessed from the`chart.id` and `subchart.id` attributes.
```{important}
`width` and `height` must be given as a number between 0 and 1.

View File

@ -4,6 +4,7 @@
:maxdepth: 2
docs
Github Repository <https://github.com/louisnw01/lightweight-charts-python>
```
```{include} ../../README.md

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,82 +0,0 @@
from typing import Literal, Union
from uuid import UUID
import webview
from multiprocessing import Queue
from lightweight_charts.js import LWC
_q = Queue()
_result_q = Queue()
class Webview(LWC):
def __init__(self, chart):
super().__init__(chart.volume_enabled, chart.inner_width, chart.inner_height)
self.chart = chart
self.started = False
self._js_api_code = 'pywebview.api.onClick'
self.webview = webview.create_window('', html=self._html, on_top=chart.on_top, js_api=self._js_api,
width=chart.width, height=chart.height, x=chart.x, y=chart.y)
self.webview.events.loaded += self._on_js_load
def run_script(self, script): self.webview.evaluate_js(script)
def _on_js_load(self):
self.loaded = True
while len(self.js_queue) > 0:
func, args, kwargs = self.js_queue[0]
if 'SUB' in func:
c_id = args[0]
args = args[1:]
getattr(self._subcharts[c_id], func.replace('SUB', ''))(*args)
else:
getattr(self, func)(*args)
del self.js_queue[0]
_loop(self.chart, controller=self)
def show(self):
if self.loaded:
self.webview.show()
else:
webview.start(debug=self.chart.debug)
def hide(self): self.webview.hide()
def exit(self):
self.webview.destroy()
del self
def create_line(self, color: str = 'rgba(214, 237, 255, 0.6)', width: int = 2):
return super().create_line(color, width).id
def create_subchart(self, volume_enabled: bool = True, position: Literal['left', 'right', 'top', 'bottom'] = 'left',
width: float = 0.5, height: float = 0.5, sync: Union[bool, UUID] = False):
return super()._pywebview_subchart(volume_enabled, position, width, height, sync)
def _loop(chart, controller=None):
wv = Webview(chart) if not controller else controller
chart._result_q.put(wv.id)
while 1:
obj = wv
func, args = chart._q.get()
if 'SUB' in func:
obj = obj._subcharts[args[0]]
args = args[1:]
func = func.replace('SUB', '')
try:
result = getattr(obj, func)(*args)
except KeyError as e:
return
if func == 'show':
chart._exit.set()
elif func == 'exit':
chart._exit.set()
chart._result_q.put(result) if result is not None else None

View File

@ -21,71 +21,56 @@ class ColorError(ValueError):
return f'{self.msg}'
class IDGen(list):
def generate(self):
var = ''.join(choices(ascii_lowercase, k=8))
if var not in self:
self.append(var)
return var
self.generate()
def _valid_color(string):
if string[:3] == 'rgb' or string[:4] == 'rgba' or string[0] == '#':
return True
raise ColorError('Colors must be in the format of either rgb, rgba or hex.')
LINE_TYPE = Literal['solid', 'dotted', 'dashed', 'large_dashed', 'sparse_dotted']
def _js_bool(b: bool): return 'true' if b is True else 'false' if b is False else None
POSITION = Literal['above', 'below', 'inside']
SHAPE = Literal['arrow_up', 'arrow_down', 'circle', 'square']
LINE_STYLE = Literal['solid', 'dotted', 'dashed', 'large_dashed', 'sparse_dotted']
MARKER_POSITION = Literal['above', 'below', 'inside']
MARKER_SHAPE = Literal['arrow_up', 'arrow_down', 'circle', 'square']
CROSSHAIR_MODE = Literal['normal', 'magnet']
PRICE_SCALE_MODE = Literal['normal', 'logarithmic', 'percentage', 'index100']
def _line_type(lt: LINE_TYPE):
return {
'solid': 'Solid',
'dotted': 'Dotted',
'dashed': 'Dashed',
'large_dashed': 'LargeDashed',
'sparse_dotted': 'SparseDotted',
None: None,
}[lt]
def _line_style(line: LINE_STYLE):
js = 'LightweightCharts.LineStyle.'
return js+line[:line.index('_')].title() + line[line.index('_') + 1:].title() if '_' in line else js+line.title()
def _position(p: POSITION):
def _crosshair_mode(mode: CROSSHAIR_MODE):
return f'LightweightCharts.CrosshairMode.{mode.title()}' if mode else None
def _price_scale_mode(mode: PRICE_SCALE_MODE):
return f"LightweightCharts.PriceScaleMode.{'IndexedTo100' if mode == 'index100' else mode.title() if mode else None}"
def _marker_shape(shape: MARKER_SHAPE):
return shape[:shape.index('_')]+shape[shape.index('_')+1:].title() if '_' in shape else shape.title()
def _marker_position(p: MARKER_POSITION):
return {
'above': 'aboveBar',
'below': 'belowBar',
'inside': 'inBar',
None: None,
}[p]
def _shape(shape: SHAPE):
return {
'arrow_up': 'arrowUp',
'arrow_down': 'arrowDown',
'circle': 'Circle',
'square': 'Square',
None: None,
}[shape]
def _crosshair_mode(mode: CROSSHAIR_MODE): return mode.title() if mode else None
def _js_bool(b: bool): return 'true' if b is True else 'false' if b is False else None
def _price_scale_mode(mode: PRICE_SCALE_MODE):
return 'IndexedTo100' if mode == 'index100' else mode.title() if mode else None
class IDGen:
def __init__(self):
self.list = []
def generate(self):
var = ''.join(choices(ascii_lowercase, k=8))
if var in self.list:
self.generate()
else:
self.list.append(var)
return var

View File

@ -21,24 +21,16 @@ class WxChart(LWC):
super().__init__(volume_enabled, inner_width=inner_width, inner_height=inner_height)
self.webview.AddScriptMessageHandler('wx_msg')
self._script_func = self.webview.RunScript
self._js_api_code = 'window.wx_msg.postMessage'
self.webview.Bind(wx.html2.EVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, lambda e: self._js_api.onClick(eval(e.GetString())))
self.webview.AddScriptMessageHandler('wx_msg')
self.webview.Bind(wx.html2.EVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, lambda e: self._js_api.onClick(eval(e.GetString())))
self.webview.Bind(wx.html2.EVT_WEBVIEW_LOADED, self._on_js_load)
self.webview.SetPage(self._html, '')
self._create_chart()
def run_script(self, script): self.webview.RunScript(script)
def _on_js_load(self, e):
self.loaded = True
for func, args, kwargs in self.js_queue:
if 'SUB' in func:
c_id = args[0]
args = args[1:]
getattr(self._subcharts[c_id], func.replace('SUB', ''))(*args)
else:
getattr(self, func)(*args)
def _on_js_load(self, e): super()._on_js_load()
def get_webview(self): return self.webview
@ -49,18 +41,13 @@ class QtChart(LWC):
self.webview = QWebEngineView(widget)
except NameError:
raise ModuleNotFoundError('QWebEngineView was not found, and must be installed to use QtChart.')
super().__init__(volume_enabled, inner_width=inner_width, inner_height=inner_height)
self._script_func = self.webview.page().runJavaScript
self.webview.loadFinished.connect(self._on_js_load)
self.webview.page().setHtml(self._html)
def run_script(self, script): self.webview.page().runJavaScript(script)
def _on_js_load(self):
self.loaded = True
for func, args, kwargs in self.js_queue:
getattr(super(), func)(*args, **kwargs)
self._create_chart()
def get_webview(self): return self.webview

View File

@ -5,7 +5,7 @@ with open('README.md', 'r', encoding='utf-8') as f:
setup(
name='lightweight_charts',
version='1.0.6',
version='1.0.7',
packages=find_packages(),
python_requires='>=3.9',
install_requires=[