- 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:
15
README.md
15
README.md
@ -6,7 +6,7 @@
|
||||
[](https://lightweight-charts-python.readthedocs.io/en/latest/docs.html)
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
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)
|
||||
|
||||
```
|
||||

|
||||

|
||||
___
|
||||
|
||||
### 2. Updating bars in real-time:
|
||||
@ -76,7 +76,7 @@ if __name__ == '__main__':
|
||||
|
||||
```
|
||||
|
||||

|
||||

|
||||
___
|
||||
|
||||
### 3. Updating bars from tick data in real-time:
|
||||
@ -106,7 +106,7 @@ if __name__ == '__main__':
|
||||
sleep(0.3)
|
||||
|
||||
```
|
||||

|
||||

|
||||
___
|
||||
|
||||
### 4. Line Indicators:
|
||||
@ -140,7 +140,7 @@ if __name__ == '__main__':
|
||||
chart.show(block=True)
|
||||
|
||||
```
|
||||

|
||||

|
||||
___
|
||||
|
||||
### 5. Styling:
|
||||
@ -177,7 +177,7 @@ if __name__ == '__main__':
|
||||
chart.show(block=True)
|
||||
|
||||
```
|
||||

|
||||

|
||||
___
|
||||
|
||||
### 6. Callbacks:
|
||||
@ -203,7 +203,7 @@ if __name__ == '__main__':
|
||||
chart.show(block=True)
|
||||
|
||||
```
|
||||

|
||||

|
||||
___
|
||||
|
||||
[](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._
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
project = 'lightweight-charts-python'
|
||||
copyright = '2023, louisnw'
|
||||
author = 'louisnw'
|
||||
release = '1.0.3'
|
||||
release = '1.0.7'
|
||||
|
||||
extensions = ["myst_parser"]
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
[](https://github.com/louisnw01/lightweight-charts-python/blob/main/LICENSE)
|
||||
[](https://github.com/louisnw01/lightweight-charts-python)
|
||||
[](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.
|
||||
|
||||
@ -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
@ -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
|
||||
|
||||
@ -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
|
||||
}[p]
|
||||
@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user