Breaking Changes:
- Removed the `api` parameter; callbacks no longer need to be in a specific class.
- Topbar callbacks now take a chart as an argument (see updated callback examples)
- Removed the `topbar` parameter from chart declaration. The Topbar will be automatically created upon declaration of a topbar widget.
- Removed the `searchbox` parameter from charts. It will be created upon subscribing to it in `chart.events`.
- Removed `dynamic_loading`.
- Removed ‘volume_enabled’ parameter. Volume will be enabled if the volumn column is present in the dataframe.
- Widgets’ `func` parameter is now declared last.
- Switchers take a tuple of options rather than a variable number of arguments.
- `add_hotkey` renamed to `hotkey`
- Horizontal lines now take a `func` argument rather than `interactive`. This event will emit the Line object that was moved.
- Removed the `name` parameter from `line.set`. Line object names are now declared upon creation.
Enhancements:
- Added the `button` widget to the Topbar.
- Added the color picker to the drawing context menu.
- Charts now have a `candle_data` method, which returns the current data displayed on the chart as a DataFrame.
- Fixed callbacks are now located in the `chart.events` object:
- search (e.g `chart.events.search += on_search`)
- new_bar
- range_change
- Added volume to the legend
- Drawings can now be accessed through `chart.toolbox.drawings`
- added the `style` and `name` parameters to `create_line`
Bug Fixes:
- Fixed a bug causing new charts not to load after `exit` was called.
- Refactored rayline placement to ensure they do not move the visible range.
- Fixed a bug causing the visible range to shift when trendlines are moved past the final candlestick.
- Fixed a bug preventing trendlines and raylines on irregular timeframes.
- Fixed a bug causing the legend to prevent mouse input into the chart.
144 lines
5.2 KiB
Python
144 lines
5.2 KiB
Python
import asyncio
|
|
import multiprocessing as mp
|
|
import webview
|
|
|
|
from lightweight_charts.abstract import LWC
|
|
|
|
|
|
chart = None
|
|
num_charts = 0
|
|
|
|
|
|
class CallbackAPI:
|
|
def __init__(self, emit_queue, return_queue):
|
|
self.emit_q, self.return_q = emit_queue, return_queue
|
|
|
|
def callback(self, message: str):
|
|
name, args = message.split('_~_')
|
|
self.return_q.put(*args) if name == 'return' else self.emit_q.put((name, args.split(';;;')))
|
|
|
|
|
|
class PyWV:
|
|
def __init__(self, q, start: mp.Event, exit, loaded, html, width, height, x, y, on_top, maximize, debug, emit_queue, return_queue):
|
|
if maximize:
|
|
width, height = webview.screens[0].width, webview.screens[0].height
|
|
self.queue = q
|
|
self.exit = exit
|
|
self.callback_api = CallbackAPI(emit_queue, return_queue)
|
|
self.loaded: list = loaded
|
|
|
|
self.windows = []
|
|
self.create_window(html, on_top, width, height, x, y)
|
|
|
|
start.wait()
|
|
webview.start(debug=debug)
|
|
self.exit.set()
|
|
|
|
def create_window(self, html, on_top, width, height, x, y):
|
|
self.windows.append(webview.create_window(
|
|
'', html=html, on_top=on_top, js_api=self.callback_api,
|
|
width=width, height=height, x=x, y=y, background_color='#000000'))
|
|
self.windows[-1].events.loaded += lambda: self.loop(self.loaded[len(self.windows)-1])
|
|
|
|
def loop(self, loaded):
|
|
loaded.set()
|
|
while 1:
|
|
i, arg = self.queue.get()
|
|
if i == 'create_window':
|
|
self.create_window(*arg)
|
|
elif arg in ('show', 'hide'):
|
|
getattr(self.windows[i], arg)()
|
|
elif arg == 'exit':
|
|
self.exit.set()
|
|
else:
|
|
try:
|
|
self.windows[i].evaluate_js(arg)
|
|
except KeyError:
|
|
return
|
|
|
|
|
|
class Chart(LWC):
|
|
def __init__(self, width: int = 800, height: int = 600, x: int = None, y: int = None,
|
|
on_top: bool = False, maximize: bool = False, debug: bool = False, toolbox: bool = False,
|
|
inner_width: float = 1.0, inner_height: float = 1.0, scale_candles_only: bool = False):
|
|
super().__init__(inner_width, inner_height, scale_candles_only, toolbox, 'pywebview.api.callback')
|
|
global chart, num_charts
|
|
|
|
if chart:
|
|
self._q, self._exit, self._start, self._process = chart._q, chart._exit, chart._start, chart._process
|
|
self._emit_q, self._return_q = mp.Queue(), mp.Queue()
|
|
for key, val in self._handlers.items():
|
|
chart._handlers[key] = val
|
|
self._handlers = chart._handlers
|
|
self._loaded = chart._loaded_list[num_charts]
|
|
self._q.put(('create_window', (self._html, on_top, width, height, x, y)))
|
|
else:
|
|
self._q, self._emit_q, self._return_q = (mp.Queue() for _ in range(3))
|
|
self._loaded_list = [mp.Event() for _ in range(10)]
|
|
self._loaded = self._loaded_list[0]
|
|
self._exit, self._start = (mp.Event() for _ in range(2))
|
|
self._process = mp.Process(target=PyWV, args=(self._q, self._start, self._exit, self._loaded_list, self._html,
|
|
width, height, x, y, on_top, maximize, debug,
|
|
self._emit_q, self._return_q), daemon=True)
|
|
self._process.start()
|
|
chart = self
|
|
|
|
self.i = num_charts
|
|
num_charts += 1
|
|
self._script_func = lambda s: self._q.put((self.i, s))
|
|
|
|
def show(self, block: bool = False):
|
|
"""
|
|
Shows the chart window.\n
|
|
:param block: blocks execution until the chart is closed.
|
|
"""
|
|
if not self.loaded:
|
|
self._start.set()
|
|
self._loaded.wait()
|
|
self._on_js_load()
|
|
else:
|
|
self._q.put((self.i, 'show'))
|
|
if block:
|
|
asyncio.run(self.show_async(block=True))
|
|
|
|
async def show_async(self, block=False):
|
|
self.show(block=False)
|
|
if not block:
|
|
asyncio.create_task(self.show_async(block=True))
|
|
return
|
|
try:
|
|
while 1:
|
|
while self._emit_q.empty() and not self._exit.is_set() and self.polygon._q.empty():
|
|
await asyncio.sleep(0.05)
|
|
if self._exit.is_set():
|
|
self._exit.clear()
|
|
return
|
|
elif not self._emit_q.empty():
|
|
name, args = self._emit_q.get()
|
|
func = self._handlers[name]
|
|
await func(*args) if asyncio.iscoroutinefunction(func) else func(*args)
|
|
continue
|
|
value = self.polygon._q.get()
|
|
func, args = value[0], value[1:]
|
|
func(*args)
|
|
except KeyboardInterrupt:
|
|
return
|
|
|
|
def hide(self):
|
|
"""
|
|
Hides the chart window.\n
|
|
"""
|
|
self._q.put((self.i, 'hide'))
|
|
|
|
def exit(self):
|
|
"""
|
|
Exits and destroys the chart window.\n
|
|
"""
|
|
global num_charts, chart
|
|
chart = None
|
|
num_charts = 0
|
|
self._q.put((self.i, 'exit'))
|
|
self._exit.wait()
|
|
self._process.terminate()
|
|
del self
|