bn-lightweight-charts 0.2.0__py3-none-any.whl
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.
- bn_lightweight_charts/__init__.py +7 -0
- bn_lightweight_charts/abstract.py +1001 -0
- bn_lightweight_charts/chart.py +241 -0
- bn_lightweight_charts/drawings.py +278 -0
- bn_lightweight_charts/js/bundle.dev.js +2472 -0
- bn_lightweight_charts/js/bundle.js +1 -0
- bn_lightweight_charts/js/index.html +25 -0
- bn_lightweight_charts/js/index_bn.html +144 -0
- bn_lightweight_charts/js/lightweight-charts.js +15475 -0
- bn_lightweight_charts/js/lightweight-charts.standalone.development.js +15475 -0
- bn_lightweight_charts/js/styles.css +257 -0
- bn_lightweight_charts/polygon.py +470 -0
- bn_lightweight_charts/table.py +138 -0
- bn_lightweight_charts/toolbox.py +45 -0
- bn_lightweight_charts/topbar.py +128 -0
- bn_lightweight_charts/util.py +227 -0
- bn_lightweight_charts/widgets.py +357 -0
- bn_lightweight_charts-0.2.0.dist-info/METADATA +317 -0
- bn_lightweight_charts-0.2.0.dist-info/RECORD +21 -0
- bn_lightweight_charts-0.2.0.dist-info/WHEEL +4 -0
- bn_lightweight_charts-0.2.0.dist-info/licenses/LICENSE +22 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import multiprocessing as mp
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from . import abstract
|
|
7
|
+
from .util import parse_event_message, FLOAT
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import threading
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CallbackAPI:
|
|
14
|
+
def __init__(self, emit_queue):
|
|
15
|
+
self.emit_queue = emit_queue
|
|
16
|
+
|
|
17
|
+
def callback(self, message: str):
|
|
18
|
+
self.emit_queue.put(message)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PyWV:
|
|
22
|
+
|
|
23
|
+
def __init__(self, q, emit_q, return_q, loaded_event):
|
|
24
|
+
self.queue = q
|
|
25
|
+
self.return_queue = return_q
|
|
26
|
+
self.emit_queue = emit_q
|
|
27
|
+
self.loaded_event = loaded_event
|
|
28
|
+
|
|
29
|
+
self.is_alive = True
|
|
30
|
+
|
|
31
|
+
import webview
|
|
32
|
+
|
|
33
|
+
self.callback_api = CallbackAPI(emit_q)
|
|
34
|
+
self.windows: typing.List[webview.Window] = []
|
|
35
|
+
self.loop()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def create_window(
|
|
39
|
+
self, width, height, x, y, screen=None, on_top=False,
|
|
40
|
+
maximize=False, title=''
|
|
41
|
+
):
|
|
42
|
+
import webview
|
|
43
|
+
|
|
44
|
+
screen = webview.screens[screen] if screen is not None else None
|
|
45
|
+
if maximize:
|
|
46
|
+
if screen is None:
|
|
47
|
+
active_screen = webview.screens[0]
|
|
48
|
+
width, height = active_screen.width, active_screen.height
|
|
49
|
+
else:
|
|
50
|
+
width, height = screen.width, screen.height
|
|
51
|
+
|
|
52
|
+
self.windows.append(webview.create_window(
|
|
53
|
+
title,
|
|
54
|
+
url=abstract.INDEX,
|
|
55
|
+
js_api=self.callback_api,
|
|
56
|
+
width=width,
|
|
57
|
+
height=height,
|
|
58
|
+
x=x,
|
|
59
|
+
y=y,
|
|
60
|
+
screen=screen,
|
|
61
|
+
on_top=on_top,
|
|
62
|
+
background_color='#000000')
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
self.windows[-1].events.loaded += lambda: self.loaded_event.set()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def loop(self):
|
|
69
|
+
import webview
|
|
70
|
+
from webview.errors import JavascriptException
|
|
71
|
+
|
|
72
|
+
# self.loaded_event.set()
|
|
73
|
+
while self.is_alive:
|
|
74
|
+
i, arg = self.queue.get()
|
|
75
|
+
|
|
76
|
+
if i == 'start':
|
|
77
|
+
webview.start(debug=arg, func=self.loop)
|
|
78
|
+
self.is_alive = False
|
|
79
|
+
self.emit_queue.put('exit')
|
|
80
|
+
return
|
|
81
|
+
if i == 'create_window':
|
|
82
|
+
self.create_window(*arg)
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
window = self.windows[i]
|
|
86
|
+
if arg == 'show':
|
|
87
|
+
window.show()
|
|
88
|
+
elif arg == 'hide':
|
|
89
|
+
window.hide()
|
|
90
|
+
else:
|
|
91
|
+
try:
|
|
92
|
+
if '_~_~RETURN~_~_' in arg:
|
|
93
|
+
self.return_queue.put(window.evaluate_js(arg[14:]))
|
|
94
|
+
else:
|
|
95
|
+
window.evaluate_js(arg)
|
|
96
|
+
except KeyError as e:
|
|
97
|
+
return
|
|
98
|
+
except JavascriptException as e:
|
|
99
|
+
msg = eval(str(e))
|
|
100
|
+
raise JavascriptException(f"\n\nscript -> '{arg}',\nerror -> {msg['name']}[{msg['line']}:{msg['column']}]\n{msg['message']}")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class WebviewHandler():
|
|
104
|
+
def __init__(self) -> None:
|
|
105
|
+
self._reset()
|
|
106
|
+
self.debug = False
|
|
107
|
+
|
|
108
|
+
def _reset(self):
|
|
109
|
+
self.loaded_event = mp.Event()
|
|
110
|
+
self.return_queue = mp.Queue()
|
|
111
|
+
self.function_call_queue = mp.Queue()
|
|
112
|
+
self.emit_queue = mp.Queue()
|
|
113
|
+
self.wv_process = mp.Process(
|
|
114
|
+
target=PyWV, args=(
|
|
115
|
+
self.function_call_queue, self.emit_queue,
|
|
116
|
+
self.return_queue, self.loaded_event
|
|
117
|
+
),
|
|
118
|
+
daemon=True
|
|
119
|
+
)
|
|
120
|
+
self.max_window_num = -1
|
|
121
|
+
|
|
122
|
+
def create_window(
|
|
123
|
+
self, width, height, x, y, screen=None, on_top=False,
|
|
124
|
+
maximize=False, title=''
|
|
125
|
+
):
|
|
126
|
+
self.function_call_queue.put((
|
|
127
|
+
'create_window', (width, height, x, y, screen, on_top, maximize, title)
|
|
128
|
+
))
|
|
129
|
+
self.max_window_num += 1
|
|
130
|
+
return self.max_window_num
|
|
131
|
+
|
|
132
|
+
def start(self):
|
|
133
|
+
self.loaded_event.clear()
|
|
134
|
+
self.wv_process.start()
|
|
135
|
+
self.function_call_queue.put(('start', self.debug))
|
|
136
|
+
self.loaded_event.wait()
|
|
137
|
+
|
|
138
|
+
def show(self, window_num):
|
|
139
|
+
self.function_call_queue.put((window_num, 'show'))
|
|
140
|
+
|
|
141
|
+
def hide(self, window_num):
|
|
142
|
+
self.function_call_queue.put((window_num, 'hide'))
|
|
143
|
+
|
|
144
|
+
def evaluate_js(self, window_num, script):
|
|
145
|
+
self.function_call_queue.put((window_num, script))
|
|
146
|
+
|
|
147
|
+
def exit(self):
|
|
148
|
+
if self.wv_process.is_alive():
|
|
149
|
+
self.wv_process.terminate()
|
|
150
|
+
self.wv_process.join()
|
|
151
|
+
self._reset()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class Chart(abstract.AbstractChart):
|
|
155
|
+
_main_window_handlers = None
|
|
156
|
+
WV: WebviewHandler = WebviewHandler()
|
|
157
|
+
|
|
158
|
+
def __init__(
|
|
159
|
+
self,
|
|
160
|
+
width: int = 800,
|
|
161
|
+
height: int = 600,
|
|
162
|
+
x: int = None,
|
|
163
|
+
y: int = None,
|
|
164
|
+
title: str = '',
|
|
165
|
+
screen: int = None,
|
|
166
|
+
on_top: bool = False,
|
|
167
|
+
maximize: bool = False,
|
|
168
|
+
debug: bool = False,
|
|
169
|
+
toolbox: bool = False,
|
|
170
|
+
inner_width: float = 1.0,
|
|
171
|
+
inner_height: float = 1.0,
|
|
172
|
+
scale_candles_only: bool = False,
|
|
173
|
+
position: FLOAT = 'left'
|
|
174
|
+
):
|
|
175
|
+
Chart.WV.debug = debug
|
|
176
|
+
self._i = Chart.WV.create_window(
|
|
177
|
+
width, height, x, y, screen, on_top, maximize, title
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
window = abstract.Window(
|
|
181
|
+
script_func=lambda s: Chart.WV.evaluate_js(self._i, s),
|
|
182
|
+
js_api_code='pywebview.api.callback'
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
abstract.Window._return_q = Chart.WV.return_queue
|
|
186
|
+
|
|
187
|
+
self.is_alive = True
|
|
188
|
+
|
|
189
|
+
if Chart._main_window_handlers is None:
|
|
190
|
+
super().__init__(window, inner_width, inner_height, scale_candles_only, toolbox, position=position)
|
|
191
|
+
Chart._main_window_handlers = self.win.handlers
|
|
192
|
+
else:
|
|
193
|
+
window.handlers = Chart._main_window_handlers
|
|
194
|
+
super().__init__(window, inner_width, inner_height, scale_candles_only, toolbox, position=position)
|
|
195
|
+
|
|
196
|
+
def show(self, block: bool = False):
|
|
197
|
+
"""
|
|
198
|
+
Shows the chart window.\n
|
|
199
|
+
:param block: blocks execution until the chart is closed.
|
|
200
|
+
"""
|
|
201
|
+
if not self.win.loaded:
|
|
202
|
+
Chart.WV.start()
|
|
203
|
+
self.win.on_js_load()
|
|
204
|
+
else:
|
|
205
|
+
Chart.WV.show(self._i)
|
|
206
|
+
if block:
|
|
207
|
+
asyncio.run(self.show_async())
|
|
208
|
+
|
|
209
|
+
async def show_async(self):
|
|
210
|
+
self.show(block=False)
|
|
211
|
+
try:
|
|
212
|
+
from . import polygon
|
|
213
|
+
[asyncio.create_task(self.polygon.async_set(*args)) for args in polygon._set_on_load]
|
|
214
|
+
while 1:
|
|
215
|
+
while Chart.WV.emit_queue.empty() and self.is_alive:
|
|
216
|
+
await asyncio.sleep(0.05)
|
|
217
|
+
if not self.is_alive:
|
|
218
|
+
return
|
|
219
|
+
response = Chart.WV.emit_queue.get()
|
|
220
|
+
if response == 'exit':
|
|
221
|
+
Chart.WV.exit()
|
|
222
|
+
self.is_alive = False
|
|
223
|
+
return
|
|
224
|
+
else:
|
|
225
|
+
func, args = parse_event_message(self.win, response)
|
|
226
|
+
await func(*args) if asyncio.iscoroutinefunction(func) else func(*args)
|
|
227
|
+
except KeyboardInterrupt:
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
def hide(self):
|
|
231
|
+
"""
|
|
232
|
+
Hides the chart window.\n
|
|
233
|
+
"""
|
|
234
|
+
self._q.put((self._i, 'hide'))
|
|
235
|
+
|
|
236
|
+
def exit(self):
|
|
237
|
+
"""
|
|
238
|
+
Exits and destroys the chart window.\n
|
|
239
|
+
"""
|
|
240
|
+
Chart.WV.exit()
|
|
241
|
+
self.is_alive = False
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
from typing import Union, Optional
|
|
6
|
+
|
|
7
|
+
from .util import NUM, Pane, as_enum, LINE_STYLE, TIME, snake_to_camel, js_json
|
|
8
|
+
|
|
9
|
+
def make_js_point(chart, time, price):
|
|
10
|
+
formatted_time = chart._single_datetime_format(time)
|
|
11
|
+
return f'''{{
|
|
12
|
+
"time": {formatted_time},
|
|
13
|
+
"logical": {chart.id}.chart.timeScale()
|
|
14
|
+
.coordinateToLogical(
|
|
15
|
+
{chart.id}.chart.timeScale()
|
|
16
|
+
.timeToCoordinate({formatted_time})
|
|
17
|
+
),
|
|
18
|
+
"price": {price}
|
|
19
|
+
}}'''
|
|
20
|
+
|
|
21
|
+
class Drawing(Pane):
|
|
22
|
+
def __init__(self, chart, func=None):
|
|
23
|
+
super().__init__(chart.win)
|
|
24
|
+
self.chart = chart
|
|
25
|
+
|
|
26
|
+
def update(self, *points):
|
|
27
|
+
formatted_points = []
|
|
28
|
+
for i in range(0, len(points), 2):
|
|
29
|
+
formatted_points.append(make_js_point(self.chart, points[i], points[i + 1]))
|
|
30
|
+
self.run_script(f'{self.id}.updatePoints({", ".join(formatted_points)})')
|
|
31
|
+
print(f'{self.id}.updatePoints({", ".join(formatted_points)})')
|
|
32
|
+
|
|
33
|
+
def delete(self):
|
|
34
|
+
"""
|
|
35
|
+
Irreversibly deletes the drawing.
|
|
36
|
+
"""
|
|
37
|
+
self.run_script(f'{self.id}.detach()')
|
|
38
|
+
|
|
39
|
+
def options(self, color='#1E80F0', style='solid', width=4):
|
|
40
|
+
self.run_script(f'''{self.id}.applyOptions({{
|
|
41
|
+
lineColor: '{color}',
|
|
42
|
+
lineStyle: {as_enum(style, LINE_STYLE)},
|
|
43
|
+
width: {width},
|
|
44
|
+
}})''')
|
|
45
|
+
|
|
46
|
+
class TwoPointDrawing(Drawing):
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
drawing_type,
|
|
50
|
+
chart,
|
|
51
|
+
start_time: TIME,
|
|
52
|
+
start_value: NUM,
|
|
53
|
+
end_time: TIME,
|
|
54
|
+
end_value: NUM,
|
|
55
|
+
round: bool,
|
|
56
|
+
options: dict,
|
|
57
|
+
func=None
|
|
58
|
+
):
|
|
59
|
+
super().__init__(chart, func)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
options_string = '\n'.join(f'{key}: {val},' for key, val in options.items())
|
|
64
|
+
|
|
65
|
+
self.run_script(f'''
|
|
66
|
+
{self.id} = new Lib.{drawing_type}(
|
|
67
|
+
{make_js_point(self.chart, start_time, start_value)},
|
|
68
|
+
{make_js_point(self.chart, end_time, end_value)},
|
|
69
|
+
{{
|
|
70
|
+
{options_string}
|
|
71
|
+
}}
|
|
72
|
+
)
|
|
73
|
+
{chart.id}.series.attachPrimitive({self.id})
|
|
74
|
+
''')
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class HorizontalLine(Drawing):
|
|
78
|
+
def __init__(self, chart, price, color, width, style, text, axis_label_visible, func):
|
|
79
|
+
super().__init__(chart, func)
|
|
80
|
+
self.price = price
|
|
81
|
+
self.run_script(f'''
|
|
82
|
+
|
|
83
|
+
{self.id} = new Lib.HorizontalLine(
|
|
84
|
+
{{price: {price}}},
|
|
85
|
+
{{
|
|
86
|
+
lineColor: '{color}',
|
|
87
|
+
lineStyle: {as_enum(style, LINE_STYLE)},
|
|
88
|
+
width: {width},
|
|
89
|
+
text: `{text}`,
|
|
90
|
+
}},
|
|
91
|
+
callbackName={f"'{self.id}'" if func else 'null'}
|
|
92
|
+
)
|
|
93
|
+
{chart.id}.series.attachPrimitive({self.id})
|
|
94
|
+
''')
|
|
95
|
+
if not func:
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
def wrapper(p):
|
|
99
|
+
self.price = float(p)
|
|
100
|
+
func(chart, self)
|
|
101
|
+
|
|
102
|
+
async def wrapper_async(p):
|
|
103
|
+
self.price = float(p)
|
|
104
|
+
await func(chart, self)
|
|
105
|
+
|
|
106
|
+
self.win.handlers[self.id] = wrapper_async if asyncio.iscoroutinefunction(func) else wrapper
|
|
107
|
+
self.run_script(f'{chart.id}.toolBox?.addNewDrawing({self.id})')
|
|
108
|
+
|
|
109
|
+
def update(self, price: float):
|
|
110
|
+
"""
|
|
111
|
+
Moves the horizontal line to the given price.
|
|
112
|
+
"""
|
|
113
|
+
self.run_script(f'{self.id}.updatePoints({{price: {price}}})')
|
|
114
|
+
# self.run_script(f'{self.id}.updatePrice({price})')
|
|
115
|
+
self.price = price
|
|
116
|
+
|
|
117
|
+
def options(self, color='#1E80F0', style='solid', width=4, text=''):
|
|
118
|
+
super().options(color, style, width)
|
|
119
|
+
self.run_script(f'{self.id}.applyOptions({{text: `{text}`}})')
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class VerticalLine(Drawing):
|
|
124
|
+
def __init__(self, chart, time, color, width, style, text, func=None):
|
|
125
|
+
super().__init__(chart, func)
|
|
126
|
+
self.time = time
|
|
127
|
+
self.run_script(f'''
|
|
128
|
+
|
|
129
|
+
{self.id} = new Lib.VerticalLine(
|
|
130
|
+
{{time: {self.chart._single_datetime_format(time)}}},
|
|
131
|
+
{{
|
|
132
|
+
lineColor: '{color}',
|
|
133
|
+
lineStyle: {as_enum(style, LINE_STYLE)},
|
|
134
|
+
width: {width},
|
|
135
|
+
text: `{text}`,
|
|
136
|
+
}},
|
|
137
|
+
callbackName={f"'{self.id}'" if func else 'null'}
|
|
138
|
+
)
|
|
139
|
+
{chart.id}.series.attachPrimitive({self.id})
|
|
140
|
+
''')
|
|
141
|
+
|
|
142
|
+
def update(self, time: TIME):
|
|
143
|
+
self.run_script(f'{self.id}.updatePoints({{time: {time}}})')
|
|
144
|
+
# self.run_script(f'{self.id}.updatePrice({price})')
|
|
145
|
+
self.price = price
|
|
146
|
+
|
|
147
|
+
def options(self, color='#1E80F0', style='solid', width=4, text=''):
|
|
148
|
+
super().options(color, style, width)
|
|
149
|
+
self.run_script(f'{self.id}.applyOptions({{text: `{text}`}})')
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class RayLine(Drawing):
|
|
153
|
+
def __init__(self,
|
|
154
|
+
chart,
|
|
155
|
+
start_time: TIME,
|
|
156
|
+
value: NUM,
|
|
157
|
+
round: bool = False,
|
|
158
|
+
color: str = '#1E80F0',
|
|
159
|
+
width: int = 2,
|
|
160
|
+
style: LINE_STYLE = 'solid',
|
|
161
|
+
text: str = '',
|
|
162
|
+
func = None,
|
|
163
|
+
):
|
|
164
|
+
super().__init__(chart, func)
|
|
165
|
+
self.run_script(f'''
|
|
166
|
+
{self.id} = new Lib.RayLine(
|
|
167
|
+
{{time: {self.chart._single_datetime_format(start_time)}, price: {value}}},
|
|
168
|
+
{{
|
|
169
|
+
lineColor: '{color}',
|
|
170
|
+
lineStyle: {as_enum(style, LINE_STYLE)},
|
|
171
|
+
width: {width},
|
|
172
|
+
text: `{text}`,
|
|
173
|
+
}},
|
|
174
|
+
callbackName={f"'{self.id}'" if func else 'null'}
|
|
175
|
+
)
|
|
176
|
+
{chart.id}.series.attachPrimitive({self.id})
|
|
177
|
+
''')
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class Box(TwoPointDrawing):
|
|
183
|
+
def __init__(self,
|
|
184
|
+
chart,
|
|
185
|
+
start_time: TIME,
|
|
186
|
+
start_value: NUM,
|
|
187
|
+
end_time: TIME,
|
|
188
|
+
end_value: NUM,
|
|
189
|
+
round: bool,
|
|
190
|
+
line_color: str,
|
|
191
|
+
fill_color: str,
|
|
192
|
+
width: int,
|
|
193
|
+
style: LINE_STYLE,
|
|
194
|
+
func=None):
|
|
195
|
+
|
|
196
|
+
super().__init__(
|
|
197
|
+
"Box",
|
|
198
|
+
chart,
|
|
199
|
+
start_time,
|
|
200
|
+
start_value,
|
|
201
|
+
end_time,
|
|
202
|
+
end_value,
|
|
203
|
+
round,
|
|
204
|
+
{
|
|
205
|
+
"lineColor": f'"{line_color}"',
|
|
206
|
+
"fillColor": f'"{fill_color}"',
|
|
207
|
+
"width": width,
|
|
208
|
+
"lineStyle": as_enum(style, LINE_STYLE)
|
|
209
|
+
},
|
|
210
|
+
func
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class TrendLine(TwoPointDrawing):
|
|
215
|
+
def __init__(self,
|
|
216
|
+
chart,
|
|
217
|
+
start_time: TIME,
|
|
218
|
+
start_value: NUM,
|
|
219
|
+
end_time: TIME,
|
|
220
|
+
end_value: NUM,
|
|
221
|
+
round: bool,
|
|
222
|
+
line_color: str,
|
|
223
|
+
width: int,
|
|
224
|
+
style: LINE_STYLE,
|
|
225
|
+
func=None):
|
|
226
|
+
|
|
227
|
+
super().__init__(
|
|
228
|
+
"TrendLine",
|
|
229
|
+
chart,
|
|
230
|
+
start_time,
|
|
231
|
+
start_value,
|
|
232
|
+
end_time,
|
|
233
|
+
end_value,
|
|
234
|
+
round,
|
|
235
|
+
{
|
|
236
|
+
"lineColor": f'"{line_color}"',
|
|
237
|
+
"width": width,
|
|
238
|
+
"lineStyle": as_enum(style, LINE_STYLE)
|
|
239
|
+
},
|
|
240
|
+
func
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# TODO reimplement/fix
|
|
244
|
+
class VerticalSpan(Pane):
|
|
245
|
+
def __init__(self, series: 'SeriesCommon', start_time: Union[TIME, tuple, list], end_time: Optional[TIME] = None,
|
|
246
|
+
color: str = 'rgba(252, 219, 3, 0.2)'):
|
|
247
|
+
self._chart = series._chart
|
|
248
|
+
super().__init__(self._chart.win)
|
|
249
|
+
start_time, end_time = pd.to_datetime(start_time), pd.to_datetime(end_time)
|
|
250
|
+
self.run_script(f'''
|
|
251
|
+
{self.id} = {self._chart.id}.chart.addHistogramSeries({{
|
|
252
|
+
color: '{color}',
|
|
253
|
+
priceFormat: {{type: 'volume'}},
|
|
254
|
+
priceScaleId: 'vertical_line',
|
|
255
|
+
lastValueVisible: false,
|
|
256
|
+
priceLineVisible: false,
|
|
257
|
+
}})
|
|
258
|
+
{self.id}.priceScale('').applyOptions({{
|
|
259
|
+
scaleMargins: {{top: 0, bottom: 0}}
|
|
260
|
+
}})
|
|
261
|
+
''')
|
|
262
|
+
if end_time is None:
|
|
263
|
+
if isinstance(start_time, pd.DatetimeIndex):
|
|
264
|
+
data = [{'time': time.timestamp(), 'value': 1} for time in start_time]
|
|
265
|
+
else:
|
|
266
|
+
data = [{'time': start_time.timestamp(), 'value': 1}]
|
|
267
|
+
self.run_script(f'{self.id}.setData({data})')
|
|
268
|
+
else:
|
|
269
|
+
self.run_script(f'''
|
|
270
|
+
{self.id}.setData(calculateTrendLine(
|
|
271
|
+
{start_time.timestamp()}, 1, {end_time.timestamp()}, 1, {series.id}))
|
|
272
|
+
''')
|
|
273
|
+
|
|
274
|
+
def delete(self):
|
|
275
|
+
"""
|
|
276
|
+
Irreversibly deletes the vertical span.
|
|
277
|
+
"""
|
|
278
|
+
self.run_script(f'{self._chart.id}.chart.removeSeries({self.id})')
|