bn-lightweight-charts 0.2.2__py3-none-any.whl → 1.0.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.
@@ -13,7 +13,7 @@ from .topbar import TopBar
13
13
  from .util import (
14
14
  BulkRunScript, Pane, Events, IDGen, as_enum, jbool, js_json, TIME, NUM, FLOAT,
15
15
  LINE_STYLE, MARKER_POSITION, MARKER_SHAPE, CROSSHAIR_MODE,
16
- PRICE_SCALE_MODE, marker_position, marker_shape, js_data,
16
+ PRICE_SCALE_MODE, marker_position, marker_shape, js_data, js_zipdata
17
17
  )
18
18
 
19
19
  current_dir = os.path.dirname(os.path.abspath(__file__))
@@ -56,7 +56,7 @@ class Window:
56
56
  self.scripts.extend(self.final_scripts)
57
57
  for script in self.scripts:
58
58
  initial_script += f'\n{script}'
59
- self.script_func(initial_script)
59
+ self.script_func(f'(async ()=> {{ {initial_script} }})();')
60
60
 
61
61
  def run_script(self, script: str, run_last: bool = False):
62
62
  """
@@ -217,6 +217,21 @@ class SeriesCommon(Pane):
217
217
  arg = self._interval * (arg.timestamp() // self._interval)+self.offset
218
218
  return arg
219
219
 
220
+ def set_zipped(self, df: Optional[pd.DataFrame] = None, format_cols: bool = True):
221
+ if df is None or df.empty:
222
+ self.run_script(f'{self.id}.series.setData([])')
223
+ self.data = pd.DataFrame()
224
+ return
225
+ if format_cols:
226
+ df = self._df_datetime_format(df, exclude_lowercase=self.name)
227
+ if self.name:
228
+ if self.name not in df:
229
+ raise NameError(f'No column named "{self.name}".')
230
+ df = df.rename(columns={self.name: 'value'})
231
+ self.data = df.copy()
232
+ self._last_bar = df.iloc[-1]
233
+ self.run_script(f'{self.id}.series.setData(await decodeGzJSON("{js_zipdata(df)}")); ')
234
+
220
235
  def set(self, df: Optional[pd.DataFrame] = None, format_cols: bool = True):
221
236
  if df is None or df.empty:
222
237
  self.run_script(f'{self.id}.series.setData([])')
@@ -594,6 +609,47 @@ class Candlestick(SeriesCommon):
594
609
  else:
595
610
  self.run_script(f"{self._chart.id}.toolBox?.clearDrawings()")
596
611
 
612
+ def set_zipped(self, df: Optional[pd.DataFrame] = None, keep_drawings=False):
613
+ """
614
+ Sets the initial data for the chart.\n
615
+ :param df: columns: date/time, open, high, low, close, volume (if volume enabled).
616
+ :param keep_drawings: keeps any drawings made through the toolbox. Otherwise, they will be deleted.
617
+ """
618
+ if df is None or df.empty:
619
+ self.run_script(f'{self.id}.series.setData([])')
620
+ self.run_script(f'{self.id}.volumeSeries.setData([])')
621
+ self.candle_data = pd.DataFrame()
622
+ return
623
+ df = self._df_datetime_format(df)
624
+ df_copy = df.copy()
625
+ self.candle_data = df_copy[['time', 'open', 'high', 'low', 'close']]
626
+ self._last_bar = df.iloc[-1]
627
+ candle_js_data = js_zipdata(df[['time', 'open', 'high', 'low', 'close']])
628
+ self.run_script(f'{self.id}.series.setData(await decodeGzJSON("{candle_js_data}"))')
629
+
630
+ if 'volume' not in df:
631
+ return
632
+ volume = df[['time', 'volume']].rename(columns={'volume': 'value'})
633
+ volume['color'] = self._volume_down_color
634
+ volume.loc[df['close'] > df['open'], 'color'] = self._volume_up_color
635
+ volume_js_data = js_zipdata(volume)
636
+ self.run_script(f'{self.id}.volumeSeries.setData(await decodeGzJSON("{volume_js_data}"))')
637
+
638
+ for line in self._lines:
639
+ if line.name not in df.columns:
640
+ continue
641
+ line.set(df[['time', line.name]], format_cols=False)
642
+ # set autoScale to true in case the user has dragged the price scale
643
+ self.run_script(f'''
644
+ if (!{self.id}.chart.priceScale("right").options.autoScale)
645
+ {self.id}.chart.priceScale("right").applyOptions({{autoScale: true}})
646
+ ''')
647
+ # TODO keep drawings doesn't work consistenly w
648
+ if keep_drawings:
649
+ self.run_script(f'{self._chart.id}.toolBox?._drawingTool.repositionOnTime()')
650
+ else:
651
+ self.run_script(f"{self._chart.id}.toolBox?.clearDrawings()")
652
+
597
653
  def update(self, series: pd.Series, _from_tick=False):
598
654
  """
599
655
  Updates the data from a bar;
@@ -20,6 +20,34 @@
20
20
  <body>
21
21
  <div id="container"></div>
22
22
  <script src="./bundle.js"></script>
23
+ <script>
24
+ async function decodeGzipBase64(b64) {
25
+ // base64 -> Uint8Array
26
+ const bytes = Uint8Array.from(
27
+ atob(b64),
28
+ c => c.charCodeAt(0)
29
+ );
30
+
31
+ // gunzip
32
+ const stream = new DecompressionStream("gzip");
33
+ const decompressedStream = new Blob([bytes])
34
+ .stream()
35
+ .pipeThrough(stream);
36
+
37
+ // Uint8Array -> string
38
+ const decompressed = await new Response(decompressedStream).arrayBuffer();
39
+ return new TextDecoder("utf-8").decode(decompressed);
40
+ }
41
+
42
+ async function decodeGzJSON(b64) {
43
+ let s = await decodeGzipBase64(b64)
44
+ try {
45
+ return JSON.parse(s)
46
+ } catch {
47
+ return []
48
+ }
49
+ }
50
+ </script>
23
51
  </body>
24
52
  </html>
25
53
 
@@ -138,6 +138,34 @@
138
138
  </div>
139
139
 
140
140
  <script src="./bundle.js"></script>
141
+ <script>
142
+ async function decodeGzipBase64(b64) {
143
+ // base64 -> Uint8Array
144
+ const bytes = Uint8Array.from(
145
+ atob(b64),
146
+ c => c.charCodeAt(0)
147
+ );
148
+
149
+ // gunzip
150
+ const stream = new DecompressionStream("gzip");
151
+ const decompressedStream = new Blob([bytes])
152
+ .stream()
153
+ .pipeThrough(stream);
154
+
155
+ // Uint8Array -> string
156
+ const decompressed = await new Response(decompressedStream).arrayBuffer();
157
+ return new TextDecoder("utf-8").decode(decompressed);
158
+ }
159
+
160
+ async function decodeGzJSON(b64) {
161
+ let s = await decodeGzipBase64(b64)
162
+ try {
163
+ return JSON.parse(s)
164
+ } catch {
165
+ return []
166
+ }
167
+ }
168
+ </script>
141
169
  </body>
142
170
  </html>
143
171
 
@@ -1,5 +1,7 @@
1
1
  import asyncio
2
2
  import json
3
+ import gzip
4
+ import base64
3
5
  from datetime import datetime
4
6
  from zoneinfo import ZoneInfo
5
7
  from tzlocal import get_localzone_name
@@ -79,6 +81,18 @@ def js_data(data: Union[pd.DataFrame, pd.Series]):
79
81
  return json.dumps(filtered_records)
80
82
 
81
83
 
84
+ def js_zipdata(data: Union[pd.DataFrame, pd.Series]):
85
+ if isinstance(data, pd.DataFrame):
86
+ d = data.to_dict(orient='records')
87
+ filtered_records = [{k: v for k, v in record.items() if v is not None and not pd.isna(v)} for record in d]
88
+ else:
89
+ d = data.to_dict()
90
+ filtered_records = {k: v for k, v in d.items()}
91
+ raw = json.dumps(filtered_records, ensure_ascii=False).encode("utf-8")
92
+ compressed = gzip.compress(raw)
93
+ return base64.b64encode(compressed).decode("ascii")
94
+
95
+
82
96
  def snake_to_camel(s: str):
83
97
  components = s.split('_')
84
98
  return components[0] + ''.join(x.title() for x in components[1:])
@@ -177,7 +177,7 @@ class StreamlitChart(StaticLWC):
177
177
  def _load(self):
178
178
  if sthtml is None:
179
179
  raise ModuleNotFoundError('streamlit.components.v1.html was not found, and must be installed to use StreamlitChart.')
180
- sthtml(f'{self._html_init} {self._html}</script></body></html>', width=self.width, height=self.height)
180
+ sthtml(f'{self._html_init} (async ()=> {{\n{self._html}\n}})();\n </script></body></html>', width=self.width, height=self.height)
181
181
 
182
182
 
183
183
  class JupyterChart(StaticLWC):
@@ -188,7 +188,7 @@ class JupyterChart(StaticLWC):
188
188
  def _load(self):
189
189
  if HTML is None:
190
190
  raise ModuleNotFoundError('IPython.display.HTML was not found, and must be installed to use JupyterChart.')
191
- html_code = html.escape(f"{self._html_init} {self._html}</script></body></html>")
191
+ html_code = html.escape(f"{self._html_init} (async ()=> {{\n{self._html}\n}})();\n </script></body></html>")
192
192
  iframe = f'<iframe width="{self.width}" height="{self.height}" frameBorder="0" srcdoc="{html_code}"></iframe>'
193
193
  display(HTML(iframe))
194
194
 
@@ -200,13 +200,13 @@ class HTMLChart(StaticLWC):
200
200
  self.filename = filename
201
201
 
202
202
  def _load(self):
203
- html_code = f"{self._html_init} {self._html}</script></body></html>"
203
+ html_code = f"{self._html_init} (async ()=> {{\n {self._html}\n}})();\n </script></body></html>"
204
204
  with open(self.filename, 'w') as file:
205
205
  file.write(html_code)
206
206
 
207
207
 
208
208
  class HTMLChart_BN(StaticLWC):
209
- def __init__(self, width: int = 800, height=350, inner_width=1, inner_height=1,
209
+ def __init__(self, width: int = 800, height=350, inner_width=1, inner_height=1,
210
210
  scale_candles_only: bool = False, toolbox: bool = False, filename = "bn_charts.html"):
211
211
  super().__init__(width=width, height=height, inner_width=inner_width, inner_height=inner_height,
212
212
  scale_candles_only=scale_candles_only, toolbox=toolbox, autosize=True,
@@ -237,7 +237,7 @@ class HTMLChart_BN(StaticLWC):
237
237
  const perf_metrics = {json.dumps(self.performance)};
238
238
  const strategy_titles = {json.dumps(self.strat_titles)};
239
239
 
240
- function updateChart(id){{
240
+ async function updateChart(id){{
241
241
  document.querySelector('#nav-home-tab')?.click();
242
242
  {func_code}
243
243
  }}
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bn-lightweight-charts
3
- Version: 0.2.2
3
+ Version: 1.0.0
4
4
  Summary: Python framework for TradingView's Lightweight Charts JavaScript library.
5
5
  Project-URL: Homepage, https://github.com/smalinin/bn_lightweight-charts-python
6
6
  Project-URL: Source, https://github.com/smalinin/bn_lightweight-charts-python
7
7
  Author: smalinin
8
8
  License: MIT
9
9
  License-File: LICENSE
10
- Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Operating System :: OS Independent
13
13
  Classifier: Programming Language :: Python :: 3
@@ -1,19 +1,19 @@
1
1
  bn_lightweight_charts/__init__.py,sha256=5RdQFzvOLx8wBAtgxp2NTpqAuDJMMeXfUoMiXNJGiKk,202
2
- bn_lightweight_charts/abstract.py,sha256=nynPR6wjTzv7dKaAqA9hFXcM8aCM4g-SXOMEA3wC5MY,39286
2
+ bn_lightweight_charts/abstract.py,sha256=ILQoUarWvSmUqkGInuCOoFUiqLBhYEDXhSa9CglZOmY,42052
3
3
  bn_lightweight_charts/chart.py,sha256=umQ154Z8NLci2dyQJQxqvsghdjbhAJs8lsoNvScpwEk,7211
4
4
  bn_lightweight_charts/drawings.py,sha256=ANg54TShelOp7SvwQkD2P5_7_-zMTYX6tLDdCpsIcOQ,8394
5
5
  bn_lightweight_charts/polygon.py,sha256=25HhZflP0_vF_CPVrMHy3X_SBMPk8hH8yXi4Jx7eSwY,18532
6
6
  bn_lightweight_charts/table.py,sha256=72OMLQrUlWfJ7iz7VurexmWrXGzV1vkr_tpjPkm3DUk,5151
7
7
  bn_lightweight_charts/toolbox.py,sha256=PhvpXOW_tDAxDcG94uIGBK_f6gFjNH9h1xHKzQNo5zU,1521
8
8
  bn_lightweight_charts/topbar.py,sha256=nSC5_geDvJfjAJ3eBtZs7cRElhXBt7ODDBnJIGnhdJM,4660
9
- bn_lightweight_charts/util.py,sha256=X6v6IZc6CcZX7ARvmb4CedLmxzU42yVjpouxBzhcPTA,7535
10
- bn_lightweight_charts/widgets.py,sha256=g90unFqAxwQ-Z9ToAsnqOsKuqmGJYwy4FH5y_TI0rB0,13877
9
+ bn_lightweight_charts/util.py,sha256=KAvHsw-1NY0CUKynUnoQWTvuTGaKWa2_-4DI9ajYNpI,8074
10
+ bn_lightweight_charts/widgets.py,sha256=YmT5MKWhVegflsWMCeb78hfdxnSwB3yCU29g2jNT060,13967
11
11
  bn_lightweight_charts/js/bundle.js,sha256=AQyl4kOKB7g4t0wEyvaynUcMyXulUksGMjf1Bk42FKA,50708
12
- bn_lightweight_charts/js/index.html,sha256=CsPlIOqaidmQZucaJKdr1jfKWqSGP-NTuXso71AqsUw,670
13
- bn_lightweight_charts/js/index_bn.html,sha256=NFBZYk0YgOs0XOEzcC_xLZnQrA-bQprVMwBrKAS1mNc,6425
12
+ bn_lightweight_charts/js/index.html,sha256=aErKo-Pbn6esUJ5zwl66NvGhOp00Br3kP6Bejxw9sG4,1475
13
+ bn_lightweight_charts/js/index_bn.html,sha256=JLZVPnYycpyi_-geWTemBD5fE9DQ2XHu08iwa6bIS6M,7230
14
14
  bn_lightweight_charts/js/lightweight-charts.js,sha256=19vIfLfo_MW1TWxMB1NbNp82m9S84zrJd0A3R1CQl6U,180587
15
15
  bn_lightweight_charts/js/styles.css,sha256=hpr57ZOGU_Wibag2rrB6GyvObQkr6d8ySyaSLs9UnZI,4811
16
- bn_lightweight_charts-0.2.2.dist-info/METADATA,sha256=fKdOKQM9Ihk0veGkaJcIhcyCzD-aRijSLIfx3sCp43Y,9982
17
- bn_lightweight_charts-0.2.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
- bn_lightweight_charts-0.2.2.dist-info/licenses/LICENSE,sha256=c1BNniz3_GfGwKHblopMfeUQ2uiORYkfH33m7el-x7o,1094
19
- bn_lightweight_charts-0.2.2.dist-info/RECORD,,
16
+ bn_lightweight_charts-1.0.0.dist-info/METADATA,sha256=cCVFLjoPLPGSQrr3M5FgV_F_GToOxdW5XRI9996pvHE,9994
17
+ bn_lightweight_charts-1.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
18
+ bn_lightweight_charts-1.0.0.dist-info/licenses/LICENSE,sha256=c1BNniz3_GfGwKHblopMfeUQ2uiORYkfH33m7el-x7o,1094
19
+ bn_lightweight_charts-1.0.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any