first commit
This commit is contained in:
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 louisnw01
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
205
README.md
Normal file
205
README.md
Normal file
@ -0,0 +1,205 @@
|
||||
# lightweight_charts_python
|
||||
|
||||
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/).
|
||||
|
||||
## Installation
|
||||
```
|
||||
pip install lightweight_charts
|
||||
```
|
||||
___
|
||||
|
||||
## Features
|
||||
1. Simple and easy to use.
|
||||
2. Blocking or non-blocking GUI.
|
||||
3. Streamlined for live data, with methods for updating directly from tick data.
|
||||
4. Support for wxPython.
|
||||
___
|
||||
|
||||
### 1. Display data from a csv:
|
||||
|
||||
```python
|
||||
import pandas as pd
|
||||
from lightweight_charts import Chart
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
chart = Chart()
|
||||
|
||||
# Columns: | time | open | high | low | close | volume (if volume is enabled) |
|
||||
df = pd.read_csv('ohlcv.csv')
|
||||
chart.set(df)
|
||||
|
||||
chart.show(block=True)
|
||||
|
||||
```
|
||||

|
||||
___
|
||||
|
||||
### 2. Updating bars in real-time:
|
||||
|
||||
```python
|
||||
import pandas as pd
|
||||
from time import sleep
|
||||
from lightweight_charts import Chart
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
chart = Chart()
|
||||
|
||||
df1 = pd.read_csv('ohlcv.csv')
|
||||
df2 = pd.read_csv('next_ohlcv.csv')
|
||||
|
||||
chart.set(df1)
|
||||
|
||||
chart.show()
|
||||
|
||||
last_close = df1.iloc[-1]
|
||||
|
||||
for i, series in df2.iterrows():
|
||||
chart.update(series)
|
||||
|
||||
if series['close'] > 20 and last_close < 20:
|
||||
chart.marker(text='The price crossed $20!')
|
||||
|
||||
last_close = series['close']
|
||||
sleep(0.1)
|
||||
|
||||
```
|
||||
|
||||

|
||||
___
|
||||
|
||||
### 3. Updating bars from tick data in real-time:
|
||||
|
||||
```python
|
||||
import pandas as pd
|
||||
from time import sleep
|
||||
from lightweight_charts import Chart
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
df1 = pd.read_csv('ohlc.csv')
|
||||
|
||||
# Columns: | time | price | volume (if volume is enabled) |
|
||||
df2 = pd.read_csv('ticks.csv')
|
||||
|
||||
chart = Chart(volume_enabled=False)
|
||||
|
||||
chart.set(df1)
|
||||
|
||||
chart.show()
|
||||
|
||||
for i, tick in df2.iterrows():
|
||||
chart.update_from_tick(tick)
|
||||
|
||||
sleep(0.3)
|
||||
|
||||
```
|
||||

|
||||
___
|
||||
|
||||
### 4. Line Indicators:
|
||||
|
||||
```python
|
||||
import pandas as pd
|
||||
from lightweight_charts import Chart
|
||||
|
||||
|
||||
def calculate_sma(data: pd.DataFrame, period: int = 50):
|
||||
def avg(d: pd.DataFrame):
|
||||
return d['close'].mean()
|
||||
result = []
|
||||
for i in range(period - 1, len(data)):
|
||||
val = avg(data.iloc[i - period + 1:i])
|
||||
result.append({'time': data.iloc[i]['date'], 'value': val})
|
||||
return pd.DataFrame(result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
chart = Chart()
|
||||
|
||||
df = pd.read_csv('ohlcv.csv')
|
||||
chart.set(df)
|
||||
|
||||
line = chart.create_line()
|
||||
sma_data = calculate_sma(df)
|
||||
line.set(sma_data)
|
||||
|
||||
chart.show(block=True)
|
||||
|
||||
```
|
||||

|
||||
___
|
||||
|
||||
### 5. Styling:
|
||||
|
||||
```python
|
||||
import pandas as pd
|
||||
from lightweight_charts import Chart
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
chart = Chart(debug=True)
|
||||
|
||||
df = pd.read_csv('ohlcv.csv')
|
||||
|
||||
chart.layout(background_color='#090008', text_color='#FFFFFF', font_size=16, font_family='Helvetica')
|
||||
|
||||
chart.candle_style(up_color='#00ff55', down_color='#ed4807', border_up_color='#FFFFFF', border_down_color='#FFFFFF',
|
||||
wick_up_color='#FFFFFF', wick_down_color='#FFFFFF')
|
||||
|
||||
chart.volume_config(up_color='#00ff55', down_color='#ed4807')
|
||||
|
||||
chart.watermark('1D', color='rgba(180, 180, 240, 0.7)')
|
||||
|
||||
chart.crosshair(mode='normal', vert_color='#FFFFFF', vert_style='dotted', horz_color='#FFFFFF', horz_style='dotted')
|
||||
|
||||
chart.legend(visible=True, font_size=14)
|
||||
|
||||
chart.set(df)
|
||||
|
||||
chart.show(block=True)
|
||||
|
||||
```
|
||||

|
||||
___
|
||||
|
||||
### 6. Callbacks:
|
||||
|
||||
```python
|
||||
import pandas as pd
|
||||
from lightweight_charts import Chart
|
||||
|
||||
|
||||
def on_click(bar: dict):
|
||||
print(f"Time: {bar['time']} | Close: {bar['close']}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
chart = Chart()
|
||||
|
||||
df = pd.read_csv('ohlcv.csv')
|
||||
chart.set(df)
|
||||
|
||||
chart.subscribe_click(on_click)
|
||||
|
||||
chart.show(block=True)
|
||||
|
||||
```
|
||||

|
||||
___
|
||||
## Wiki
|
||||
|
||||
For a detailed guide of lightweight-charts-python, view the wiki [here](https://github.com/louisnw01/lightweight-charts-python/wiki).
|
||||
|
||||
___
|
||||
|
||||
_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._
|
||||
|
||||
|
||||
|
||||
2982
examples/1_setting_data/ohlcv.csv
Normal file
2982
examples/1_setting_data/ohlcv.csv
Normal file
File diff suppressed because it is too large
Load Diff
BIN
examples/1_setting_data/setting_data.png
Normal file
BIN
examples/1_setting_data/setting_data.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 492 KiB |
12
examples/1_setting_data/setting_data.py
Normal file
12
examples/1_setting_data/setting_data.py
Normal file
@ -0,0 +1,12 @@
|
||||
import pandas as pd
|
||||
from lightweight_charts import Chart
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
chart = Chart()
|
||||
|
||||
# Columns: | time | open | high | low | close | volume (if volume is enabled) |
|
||||
df = pd.read_csv('ohlcv.csv')
|
||||
chart.set(df)
|
||||
|
||||
chart.show(block=True)
|
||||
BIN
examples/2_live_data/live_data.gif
Normal file
BIN
examples/2_live_data/live_data.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.6 MiB |
25
examples/2_live_data/live_data.py
Normal file
25
examples/2_live_data/live_data.py
Normal file
@ -0,0 +1,25 @@
|
||||
import pandas as pd
|
||||
from time import sleep
|
||||
from lightweight_charts import Chart
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
chart = Chart()
|
||||
|
||||
df1 = pd.read_csv('ohlcv.csv')
|
||||
df2 = pd.read_csv('next_ohlcv.csv')
|
||||
|
||||
chart.set(df1)
|
||||
|
||||
chart.show()
|
||||
|
||||
last_close = df1.iloc[-1]
|
||||
|
||||
for i, series in df2.iterrows():
|
||||
chart.update(series)
|
||||
|
||||
if series['close'] > 20 and last_close < 20:
|
||||
chart.marker(text='The price crossed $20!')
|
||||
|
||||
last_close = series['close']
|
||||
sleep(0.1)
|
||||
1492
examples/2_live_data/next_ohlcv.csv
Normal file
1492
examples/2_live_data/next_ohlcv.csv
Normal file
File diff suppressed because it is too large
Load Diff
1491
examples/2_live_data/ohlcv.csv
Normal file
1491
examples/2_live_data/ohlcv.csv
Normal file
File diff suppressed because it is too large
Load Diff
437
examples/3_tick_data/ohlc.csv
Normal file
437
examples/3_tick_data/ohlc.csv
Normal file
@ -0,0 +1,437 @@
|
||||
,date,open,high,low,close,average,barCount
|
||||
0,2023-05-04 08:00:00+00:00,162.61,162.61,161.99,162.1,162.232,26
|
||||
1,2023-05-04 08:01:00+00:00,162.15,162.29,162.13,162.28,162.231,11
|
||||
2,2023-05-04 08:02:00+00:00,162.18,162.48,162.15,162.48,162.27,17
|
||||
3,2023-05-04 08:03:00+00:00,162.44,162.46,162.44,162.45,162.45,4
|
||||
4,2023-05-04 08:04:00+00:00,162.46,162.59,162.45,162.55,162.53,15
|
||||
5,2023-05-04 08:05:00+00:00,162.55,162.55,162.29,162.35,162.395,14
|
||||
6,2023-05-04 08:06:00+00:00,162.42,162.47,162.4,162.4,162.434,7
|
||||
7,2023-05-04 08:07:00+00:00,162.41,162.41,162.35,162.37,162.388,4
|
||||
8,2023-05-04 08:08:00+00:00,162.37,162.4,162.37,162.39,162.384,7
|
||||
9,2023-05-04 08:09:00+00:00,162.38,162.41,162.37,162.4,162.391,7
|
||||
10,2023-05-04 08:10:00+00:00,162.38,162.38,162.28,162.28,162.331,13
|
||||
11,2023-05-04 08:11:00+00:00,162.27,162.27,162.14,162.19,162.193,7
|
||||
12,2023-05-04 08:12:00+00:00,162.22,162.24,162.17,162.24,162.21,4
|
||||
13,2023-05-04 08:13:00+00:00,162.2,162.25,162.17,162.25,162.215,4
|
||||
14,2023-05-04 08:14:00+00:00,162.36,162.4,162.36,162.4,162.38,2
|
||||
15,2023-05-04 08:15:00+00:00,162.42,162.42,162.42,162.42,162.42,2
|
||||
16,2023-05-04 08:16:00+00:00,162.47,162.47,162.47,162.47,162.47,3
|
||||
17,2023-05-04 08:17:00+00:00,162.47,162.5,162.47,162.5,162.48,3
|
||||
18,2023-05-04 08:18:00+00:00,162.59,162.7,162.59,162.7,162.686,8
|
||||
19,2023-05-04 08:19:00+00:00,162.68,162.68,162.68,162.68,162.68,1
|
||||
20,2023-05-04 08:20:00+00:00,162.69,162.72,162.68,162.7,162.694,5
|
||||
21,2023-05-04 08:21:00+00:00,162.69,162.73,162.69,162.73,162.703,10
|
||||
22,2023-05-04 08:22:00+00:00,162.68,162.68,162.68,162.68,162.68,2
|
||||
23,2023-05-04 08:23:00+00:00,162.7,162.7,162.65,162.65,162.675,4
|
||||
24,2023-05-04 08:24:00+00:00,162.65,162.7,162.65,162.68,162.668,6
|
||||
25,2023-05-04 08:25:00+00:00,162.69,162.7,162.69,162.69,162.693,3
|
||||
26,2023-05-04 08:26:00+00:00,162.67,162.69,162.67,162.69,162.685,4
|
||||
27,2023-05-04 08:27:00+00:00,162.69,162.69,162.69,162.69,162.69,0
|
||||
28,2023-05-04 08:28:00+00:00,162.65,162.65,162.65,162.65,162.65,1
|
||||
29,2023-05-04 08:29:00+00:00,162.63,162.63,162.62,162.62,162.628,4
|
||||
30,2023-05-04 08:30:00+00:00,162.59,162.59,162.58,162.58,162.585,2
|
||||
31,2023-05-04 08:31:00+00:00,162.51,162.6,162.51,162.6,162.546,3
|
||||
32,2023-05-04 08:32:00+00:00,162.52,162.52,162.52,162.52,162.52,1
|
||||
33,2023-05-04 08:33:00+00:00,162.59,162.6,162.59,162.6,162.595,2
|
||||
34,2023-05-04 08:34:00+00:00,162.6,162.6,162.6,162.6,162.6,0
|
||||
35,2023-05-04 08:35:00+00:00,162.53,162.53,162.5,162.5,162.506,4
|
||||
36,2023-05-04 08:36:00+00:00,162.45,162.45,162.45,162.45,162.45,1
|
||||
37,2023-05-04 08:37:00+00:00,162.6,162.6,162.6,162.6,162.6,2
|
||||
38,2023-05-04 08:38:00+00:00,162.6,162.6,162.6,162.6,162.6,0
|
||||
39,2023-05-04 08:39:00+00:00,162.6,162.6,162.6,162.6,162.6,0
|
||||
40,2023-05-04 08:40:00+00:00,162.6,162.6,162.6,162.6,162.6,0
|
||||
41,2023-05-04 08:41:00+00:00,162.6,162.6,162.6,162.6,162.6,0
|
||||
42,2023-05-04 08:42:00+00:00,162.7,162.7,162.7,162.7,162.7,2
|
||||
43,2023-05-04 08:43:00+00:00,162.69,162.73,162.69,162.73,162.699,3
|
||||
44,2023-05-04 08:44:00+00:00,162.74,162.74,162.74,162.74,162.74,2
|
||||
45,2023-05-04 08:45:00+00:00,162.74,162.76,162.74,162.76,162.755,3
|
||||
46,2023-05-04 08:46:00+00:00,162.76,162.79,162.76,162.79,162.78,3
|
||||
47,2023-05-04 08:47:00+00:00,162.77,162.77,162.77,162.77,162.77,1
|
||||
48,2023-05-04 08:48:00+00:00,162.77,162.77,162.76,162.76,162.763,2
|
||||
49,2023-05-04 08:49:00+00:00,162.76,162.76,162.76,162.76,162.76,0
|
||||
50,2023-05-04 08:50:00+00:00,162.77,162.77,162.77,162.77,162.77,2
|
||||
51,2023-05-04 08:51:00+00:00,162.77,162.77,162.77,162.77,162.77,0
|
||||
52,2023-05-04 08:52:00+00:00,162.73,162.73,162.73,162.73,162.73,1
|
||||
53,2023-05-04 08:53:00+00:00,162.79,162.79,162.79,162.79,162.79,2
|
||||
54,2023-05-04 08:54:00+00:00,162.78,162.78,162.78,162.78,162.78,2
|
||||
55,2023-05-04 08:55:00+00:00,162.78,162.78,162.78,162.78,162.78,1
|
||||
56,2023-05-04 08:56:00+00:00,162.78,162.78,162.78,162.78,162.78,0
|
||||
57,2023-05-04 08:57:00+00:00,162.77,162.77,162.77,162.77,162.77,1
|
||||
58,2023-05-04 08:58:00+00:00,162.73,162.73,162.51,162.51,162.608,11
|
||||
59,2023-05-04 08:59:00+00:00,162.65,162.65,162.54,162.64,162.594,11
|
||||
60,2023-05-04 09:00:00+00:00,162.6,162.63,162.6,162.63,162.609,2
|
||||
61,2023-05-04 09:01:00+00:00,162.63,162.63,162.63,162.63,162.63,0
|
||||
62,2023-05-04 09:02:00+00:00,162.63,162.63,162.63,162.63,162.63,0
|
||||
63,2023-05-04 09:03:00+00:00,162.72,162.72,162.72,162.72,162.72,1
|
||||
64,2023-05-04 09:04:00+00:00,162.64,162.64,162.64,162.64,162.64,2
|
||||
65,2023-05-04 09:05:00+00:00,162.64,162.64,162.64,162.64,162.64,0
|
||||
66,2023-05-04 09:06:00+00:00,162.64,162.64,162.64,162.64,162.64,0
|
||||
67,2023-05-04 09:07:00+00:00,162.64,162.64,162.64,162.64,162.64,0
|
||||
68,2023-05-04 09:08:00+00:00,162.64,162.64,162.64,162.64,162.64,0
|
||||
69,2023-05-04 09:09:00+00:00,162.65,162.67,162.65,162.67,162.66,2
|
||||
70,2023-05-04 09:10:00+00:00,162.67,162.67,162.67,162.67,162.67,0
|
||||
71,2023-05-04 09:11:00+00:00,162.67,162.67,162.67,162.67,162.67,0
|
||||
72,2023-05-04 09:12:00+00:00,162.67,162.67,162.67,162.67,162.67,2
|
||||
73,2023-05-04 09:13:00+00:00,162.67,162.67,162.67,162.67,162.67,0
|
||||
74,2023-05-04 09:14:00+00:00,162.67,162.67,162.67,162.67,162.67,0
|
||||
75,2023-05-04 09:15:00+00:00,162.55,162.55,162.55,162.55,162.55,2
|
||||
76,2023-05-04 09:16:00+00:00,162.55,162.55,162.55,162.55,162.55,0
|
||||
77,2023-05-04 09:17:00+00:00,162.5,162.5,162.5,162.5,162.5,1
|
||||
78,2023-05-04 09:18:00+00:00,162.5,162.5,162.5,162.5,162.5,2
|
||||
79,2023-05-04 09:19:00+00:00,162.5,162.55,162.5,162.55,162.517,3
|
||||
80,2023-05-04 09:20:00+00:00,162.5,162.5,162.49,162.5,162.498,6
|
||||
81,2023-05-04 09:21:00+00:00,162.49,162.52,162.49,162.49,162.502,4
|
||||
82,2023-05-04 09:22:00+00:00,162.46,162.46,162.46,162.46,162.46,1
|
||||
83,2023-05-04 09:23:00+00:00,162.39,162.39,162.39,162.39,162.39,1
|
||||
84,2023-05-04 09:24:00+00:00,162.4,162.4,162.33,162.34,162.361,8
|
||||
85,2023-05-04 09:25:00+00:00,162.37,162.37,162.37,162.37,162.37,1
|
||||
86,2023-05-04 09:26:00+00:00,162.37,162.37,162.37,162.37,162.37,0
|
||||
87,2023-05-04 09:27:00+00:00,162.4,162.4,162.36,162.36,162.387,2
|
||||
88,2023-05-04 09:28:00+00:00,162.33,162.33,162.33,162.33,162.33,1
|
||||
89,2023-05-04 09:29:00+00:00,162.35,162.35,162.35,162.35,162.35,1
|
||||
90,2023-05-04 09:30:00+00:00,162.35,162.35,162.35,162.35,162.35,0
|
||||
91,2023-05-04 09:31:00+00:00,162.31,162.32,162.31,162.32,162.312,3
|
||||
92,2023-05-04 09:32:00+00:00,162.32,162.32,162.32,162.32,162.32,0
|
||||
93,2023-05-04 09:33:00+00:00,162.32,162.32,162.32,162.32,162.32,0
|
||||
94,2023-05-04 09:34:00+00:00,162.32,162.32,162.32,162.32,162.32,0
|
||||
95,2023-05-04 09:35:00+00:00,162.42,162.42,162.42,162.42,162.42,1
|
||||
96,2023-05-04 09:36:00+00:00,162.42,162.42,162.42,162.42,162.42,0
|
||||
97,2023-05-04 09:37:00+00:00,162.43,162.43,162.43,162.43,162.43,1
|
||||
98,2023-05-04 09:38:00+00:00,162.39,162.39,162.39,162.39,162.39,1
|
||||
99,2023-05-04 09:39:00+00:00,162.39,162.39,162.39,162.39,162.39,0
|
||||
100,2023-05-04 09:40:00+00:00,162.45,162.45,162.44,162.44,162.448,2
|
||||
101,2023-05-04 09:41:00+00:00,162.44,162.44,162.44,162.44,162.44,0
|
||||
102,2023-05-04 09:42:00+00:00,162.44,162.46,162.44,162.46,162.449,2
|
||||
103,2023-05-04 09:43:00+00:00,162.46,162.46,162.46,162.46,162.46,1
|
||||
104,2023-05-04 09:44:00+00:00,162.46,162.46,162.46,162.46,162.46,0
|
||||
105,2023-05-04 09:45:00+00:00,162.46,162.46,162.46,162.46,162.46,0
|
||||
106,2023-05-04 09:46:00+00:00,162.46,162.46,162.46,162.46,162.46,0
|
||||
107,2023-05-04 09:47:00+00:00,162.48,162.48,162.48,162.48,162.48,1
|
||||
108,2023-05-04 09:48:00+00:00,162.41,162.44,162.32,162.32,162.396,8
|
||||
109,2023-05-04 09:49:00+00:00,162.32,162.32,162.31,162.31,162.313,4
|
||||
110,2023-05-04 09:50:00+00:00,162.33,162.33,162.28,162.28,162.294,5
|
||||
111,2023-05-04 09:51:00+00:00,162.24,162.29,162.24,162.29,162.251,5
|
||||
112,2023-05-04 09:52:00+00:00,162.25,162.25,162.2,162.2,162.233,2
|
||||
113,2023-05-04 09:53:00+00:00,162.13,162.13,162.13,162.13,162.13,2
|
||||
114,2023-05-04 09:54:00+00:00,162.15,162.15,162.15,162.15,162.15,1
|
||||
115,2023-05-04 09:55:00+00:00,162.18,162.18,162.1,162.1,162.136,4
|
||||
116,2023-05-04 09:56:00+00:00,162.1,162.1,162.1,162.1,162.1,0
|
||||
117,2023-05-04 09:57:00+00:00,162.18,162.2,162.18,162.2,162.194,4
|
||||
118,2023-05-04 09:58:00+00:00,162.2,162.2,162.2,162.2,162.2,0
|
||||
119,2023-05-04 09:59:00+00:00,162.23,162.23,162.23,162.23,162.23,2
|
||||
120,2023-05-04 10:00:00+00:00,162.26,162.28,162.26,162.27,162.268,5
|
||||
121,2023-05-04 10:01:00+00:00,162.27,162.27,162.27,162.27,162.27,0
|
||||
122,2023-05-04 10:02:00+00:00,162.28,162.28,162.28,162.28,162.28,1
|
||||
123,2023-05-04 10:03:00+00:00,162.28,162.28,162.28,162.28,162.28,0
|
||||
124,2023-05-04 10:04:00+00:00,162.21,162.21,162.1,162.11,162.174,9
|
||||
125,2023-05-04 10:05:00+00:00,162.1,162.1,162.02,162.02,162.048,7
|
||||
126,2023-05-04 10:06:00+00:00,162.04,162.04,161.98,161.98,162.002,10
|
||||
127,2023-05-04 10:07:00+00:00,162.04,162.04,161.95,161.95,161.986,4
|
||||
128,2023-05-04 10:08:00+00:00,161.97,161.97,161.9,161.9,161.926,11
|
||||
129,2023-05-04 10:09:00+00:00,161.88,161.88,161.88,161.88,161.88,2
|
||||
130,2023-05-04 10:10:00+00:00,161.82,161.85,161.82,161.85,161.843,4
|
||||
131,2023-05-04 10:11:00+00:00,161.85,161.85,161.85,161.85,161.85,0
|
||||
132,2023-05-04 10:12:00+00:00,161.81,161.81,161.8,161.8,161.807,3
|
||||
133,2023-05-04 10:13:00+00:00,161.81,161.85,161.81,161.84,161.824,4
|
||||
134,2023-05-04 10:14:00+00:00,161.85,162.06,161.85,162.04,161.935,4
|
||||
135,2023-05-04 10:15:00+00:00,162.03,162.03,162.02,162.02,162.025,2
|
||||
136,2023-05-04 10:16:00+00:00,161.87,161.87,161.87,161.87,161.87,2
|
||||
137,2023-05-04 10:17:00+00:00,162.0,162.0,161.91,161.91,161.955,2
|
||||
138,2023-05-04 10:18:00+00:00,161.97,161.97,161.97,161.97,161.97,2
|
||||
139,2023-05-04 10:19:00+00:00,162.05,162.05,162.05,162.05,162.05,2
|
||||
140,2023-05-04 10:20:00+00:00,162.06,162.06,162.06,162.06,162.06,1
|
||||
141,2023-05-04 10:21:00+00:00,162.05,162.14,162.05,162.14,162.08,5
|
||||
142,2023-05-04 10:22:00+00:00,162.2,162.2,162.19,162.19,162.197,3
|
||||
143,2023-05-04 10:23:00+00:00,162.27,162.3,162.27,162.3,162.281,2
|
||||
144,2023-05-04 10:24:00+00:00,162.3,162.3,162.3,162.3,162.3,0
|
||||
145,2023-05-04 10:25:00+00:00,162.24,162.24,162.21,162.21,162.225,2
|
||||
146,2023-05-04 10:26:00+00:00,162.21,162.21,162.21,162.21,162.21,1
|
||||
147,2023-05-04 10:27:00+00:00,162.21,162.21,162.21,162.21,162.21,0
|
||||
148,2023-05-04 10:28:00+00:00,162.21,162.21,162.21,162.21,162.21,0
|
||||
149,2023-05-04 10:29:00+00:00,162.2,162.2,162.2,162.2,162.2,1
|
||||
150,2023-05-04 10:30:00+00:00,162.2,162.2,162.2,162.2,162.2,0
|
||||
151,2023-05-04 10:31:00+00:00,162.12,162.12,162.12,162.12,162.12,1
|
||||
152,2023-05-04 10:32:00+00:00,162.15,162.19,162.15,162.19,162.176,2
|
||||
153,2023-05-04 10:33:00+00:00,162.19,162.19,162.19,162.19,162.19,0
|
||||
154,2023-05-04 10:34:00+00:00,162.18,162.2,162.18,162.2,162.188,3
|
||||
155,2023-05-04 10:35:00+00:00,162.27,162.28,162.27,162.28,162.273,2
|
||||
156,2023-05-04 10:36:00+00:00,162.19,162.19,162.18,162.18,162.181,2
|
||||
157,2023-05-04 10:37:00+00:00,162.24,162.24,162.24,162.24,162.24,1
|
||||
158,2023-05-04 10:38:00+00:00,162.23,162.23,162.23,162.23,162.23,1
|
||||
159,2023-05-04 10:39:00+00:00,162.23,162.23,162.23,162.23,162.23,1
|
||||
160,2023-05-04 10:40:00+00:00,162.23,162.23,162.23,162.23,162.23,0
|
||||
161,2023-05-04 10:41:00+00:00,162.13,162.15,162.13,162.15,162.138,2
|
||||
162,2023-05-04 10:42:00+00:00,162.0,162.0,162.0,162.0,162.0,2
|
||||
163,2023-05-04 10:43:00+00:00,162.0,162.04,161.88,161.88,161.943,5
|
||||
164,2023-05-04 10:44:00+00:00,161.89,161.91,161.89,161.91,161.908,4
|
||||
165,2023-05-04 10:45:00+00:00,161.89,161.89,161.89,161.89,161.89,1
|
||||
166,2023-05-04 10:46:00+00:00,161.89,161.89,161.89,161.89,161.89,1
|
||||
167,2023-05-04 10:47:00+00:00,161.9,161.9,161.9,161.9,161.9,1
|
||||
168,2023-05-04 10:48:00+00:00,161.9,161.9,161.9,161.9,161.9,2
|
||||
169,2023-05-04 10:49:00+00:00,161.9,161.9,161.84,161.84,161.862,3
|
||||
170,2023-05-04 10:50:00+00:00,161.83,161.9,161.83,161.85,161.856,5
|
||||
171,2023-05-04 10:51:00+00:00,161.98,161.98,161.98,161.98,161.98,1
|
||||
172,2023-05-04 10:52:00+00:00,162.05,162.05,162.05,162.05,162.05,1
|
||||
173,2023-05-04 10:53:00+00:00,162.05,162.05,162.05,162.05,162.05,1
|
||||
174,2023-05-04 10:54:00+00:00,162.05,162.05,162.0,162.0,162.01,8
|
||||
175,2023-05-04 10:55:00+00:00,161.98,162.06,161.95,162.06,161.992,3
|
||||
176,2023-05-04 10:56:00+00:00,162.06,162.06,162.06,162.06,162.06,0
|
||||
177,2023-05-04 10:57:00+00:00,162.06,162.06,162.06,162.06,162.06,0
|
||||
178,2023-05-04 10:58:00+00:00,162.0,162.0,162.0,162.0,162.0,1
|
||||
179,2023-05-04 10:59:00+00:00,162.07,162.07,162.07,162.07,162.07,1
|
||||
180,2023-05-04 11:00:00+00:00,162.0,162.23,162.0,162.17,162.113,34
|
||||
181,2023-05-04 11:01:00+00:00,162.11,162.2,162.11,162.2,162.147,7
|
||||
182,2023-05-04 11:02:00+00:00,162.15,162.15,162.06,162.06,162.101,15
|
||||
183,2023-05-04 11:03:00+00:00,162.02,162.02,161.89,161.89,161.967,17
|
||||
184,2023-05-04 11:04:00+00:00,161.92,161.96,161.91,161.96,161.925,7
|
||||
185,2023-05-04 11:05:00+00:00,161.92,162.0,161.9,162.0,161.924,13
|
||||
186,2023-05-04 11:06:00+00:00,161.91,161.91,161.85,161.85,161.881,10
|
||||
187,2023-05-04 11:07:00+00:00,161.85,161.85,161.71,161.79,161.785,34
|
||||
188,2023-05-04 11:08:00+00:00,161.8,161.8,161.8,161.8,161.8,1
|
||||
189,2023-05-04 11:09:00+00:00,161.81,161.81,161.8,161.8,161.806,5
|
||||
190,2023-05-04 11:10:00+00:00,161.75,161.75,161.75,161.75,161.75,1
|
||||
191,2023-05-04 11:11:00+00:00,161.74,161.74,161.63,161.63,161.697,11
|
||||
192,2023-05-04 11:12:00+00:00,161.61,161.65,161.58,161.62,161.619,15
|
||||
193,2023-05-04 11:13:00+00:00,161.6,161.65,161.6,161.65,161.62,2
|
||||
194,2023-05-04 11:14:00+00:00,161.74,161.88,161.74,161.88,161.855,7
|
||||
195,2023-05-04 11:15:00+00:00,161.84,161.84,161.84,161.84,161.84,2
|
||||
196,2023-05-04 11:16:00+00:00,161.84,161.84,161.84,161.84,161.84,0
|
||||
197,2023-05-04 11:17:00+00:00,161.91,161.94,161.91,161.93,161.931,8
|
||||
198,2023-05-04 11:18:00+00:00,161.93,161.93,161.85,161.85,161.893,8
|
||||
199,2023-05-04 11:19:00+00:00,161.8,161.8,161.8,161.8,161.8,1
|
||||
200,2023-05-04 11:20:00+00:00,161.8,161.8,161.8,161.8,161.8,0
|
||||
201,2023-05-04 11:21:00+00:00,161.84,161.85,161.84,161.85,161.843,2
|
||||
202,2023-05-04 11:22:00+00:00,161.89,161.89,161.87,161.87,161.885,2
|
||||
203,2023-05-04 11:23:00+00:00,161.87,161.94,161.87,161.94,161.923,2
|
||||
204,2023-05-04 11:24:00+00:00,161.9,162.01,161.9,162.0,161.971,20
|
||||
205,2023-05-04 11:25:00+00:00,162.0,162.0,161.91,161.91,161.941,5
|
||||
206,2023-05-04 11:26:00+00:00,161.9,161.9,161.8,161.8,161.86,15
|
||||
207,2023-05-04 11:27:00+00:00,161.85,161.85,161.8,161.8,161.826,3
|
||||
208,2023-05-04 11:28:00+00:00,161.8,161.8,161.8,161.8,161.8,0
|
||||
209,2023-05-04 11:29:00+00:00,161.8,161.8,161.8,161.8,161.8,0
|
||||
210,2023-05-04 11:30:00+00:00,161.95,161.99,161.95,161.99,161.98,9
|
||||
211,2023-05-04 11:31:00+00:00,162.0,162.01,161.96,161.96,162.003,3
|
||||
212,2023-05-04 11:32:00+00:00,161.97,161.97,161.96,161.96,161.968,5
|
||||
213,2023-05-04 11:33:00+00:00,161.99,161.99,161.99,161.99,161.99,1
|
||||
214,2023-05-04 11:34:00+00:00,161.99,161.99,161.99,161.99,161.99,0
|
||||
215,2023-05-04 11:35:00+00:00,161.98,161.98,161.98,161.98,161.98,1
|
||||
216,2023-05-04 11:36:00+00:00,161.99,161.99,161.99,161.99,161.99,1
|
||||
217,2023-05-04 11:37:00+00:00,161.99,161.99,161.99,161.99,161.99,0
|
||||
218,2023-05-04 11:38:00+00:00,162.0,162.04,162.0,162.04,162.007,6
|
||||
219,2023-05-04 11:39:00+00:00,162.01,162.01,162.01,162.01,162.01,4
|
||||
220,2023-05-04 11:40:00+00:00,162.0,162.0,161.95,161.95,161.981,10
|
||||
221,2023-05-04 11:41:00+00:00,161.93,161.93,161.81,161.81,161.9,6
|
||||
222,2023-05-04 11:42:00+00:00,161.84,161.84,161.76,161.77,161.809,3
|
||||
223,2023-05-04 11:43:00+00:00,161.8,161.87,161.8,161.87,161.838,7
|
||||
224,2023-05-04 11:44:00+00:00,161.87,161.87,161.87,161.87,161.87,0
|
||||
225,2023-05-04 11:45:00+00:00,161.9,161.9,161.88,161.9,161.885,11
|
||||
226,2023-05-04 11:46:00+00:00,161.95,161.95,161.95,161.95,161.95,4
|
||||
227,2023-05-04 11:47:00+00:00,161.98,162.14,161.98,162.08,162.068,19
|
||||
228,2023-05-04 11:48:00+00:00,162.1,162.1,162.1,162.1,162.1,2
|
||||
229,2023-05-04 11:49:00+00:00,162.08,162.08,162.07,162.07,162.075,3
|
||||
230,2023-05-04 11:50:00+00:00,162.07,162.07,162.0,162.0,162.027,6
|
||||
231,2023-05-04 11:51:00+00:00,161.97,161.97,161.97,161.97,161.97,1
|
||||
232,2023-05-04 11:52:00+00:00,161.97,161.97,161.97,161.97,161.97,2
|
||||
233,2023-05-04 11:53:00+00:00,161.91,161.91,161.9,161.9,161.901,6
|
||||
234,2023-05-04 11:54:00+00:00,161.9,161.9,161.85,161.85,161.876,3
|
||||
235,2023-05-04 11:55:00+00:00,161.85,161.85,161.85,161.85,161.85,1
|
||||
236,2023-05-04 11:56:00+00:00,161.85,161.85,161.85,161.85,161.85,0
|
||||
237,2023-05-04 11:57:00+00:00,161.9,161.9,161.9,161.9,161.9,2
|
||||
238,2023-05-04 11:58:00+00:00,161.89,161.89,161.89,161.89,161.89,8
|
||||
239,2023-05-04 11:59:00+00:00,161.91,161.92,161.91,161.92,161.915,2
|
||||
240,2023-05-04 12:00:00+00:00,162.15,162.3,161.6,161.95,161.9,270
|
||||
241,2023-05-04 12:01:00+00:00,160.58,162.97,160.58,161.84,162.066,48
|
||||
242,2023-05-04 12:02:00+00:00,162.06,162.97,161.8,161.87,161.957,45
|
||||
243,2023-05-04 12:03:00+00:00,161.87,161.94,161.87,161.91,161.886,6
|
||||
244,2023-05-04 12:04:00+00:00,161.95,162.0,161.93,162.0,161.955,21
|
||||
245,2023-05-04 12:05:00+00:00,162.0,162.05,161.99,162.05,162.033,9
|
||||
246,2023-05-04 12:06:00+00:00,162.0,162.05,162.0,162.03,162.019,10
|
||||
247,2023-05-04 12:07:00+00:00,161.97,162.0,161.81,161.81,161.885,23
|
||||
248,2023-05-04 12:08:00+00:00,161.8,161.9,161.8,161.9,161.826,7
|
||||
249,2023-05-04 12:09:00+00:00,161.85,161.9,161.8,161.9,161.84,10
|
||||
250,2023-05-04 12:10:00+00:00,161.82,161.82,161.82,161.82,161.82,4
|
||||
251,2023-05-04 12:11:00+00:00,161.89,161.89,161.89,161.89,161.89,5
|
||||
252,2023-05-04 12:12:00+00:00,161.87,161.88,161.87,161.87,161.87,5
|
||||
253,2023-05-04 12:13:00+00:00,161.9,162.0,161.88,162.0,161.94,7
|
||||
254,2023-05-04 12:14:00+00:00,162.04,162.1,162.0,162.0,162.062,16
|
||||
255,2023-05-04 12:15:00+00:00,162.14,162.14,162.0,162.0,162.051,7
|
||||
256,2023-05-04 12:16:00+00:00,162.0,162.05,162.0,162.03,162.037,5
|
||||
257,2023-05-04 12:17:00+00:00,162.0,162.07,161.99,161.99,162.004,11
|
||||
258,2023-05-04 12:18:00+00:00,161.91,161.91,161.62,161.62,161.753,51
|
||||
259,2023-05-04 12:19:00+00:00,161.65,161.8,161.65,161.8,161.724,18
|
||||
260,2023-05-04 12:20:00+00:00,161.84,161.87,161.82,161.82,161.84,3
|
||||
261,2023-05-04 12:21:00+00:00,161.82,161.84,161.82,161.84,161.837,4
|
||||
262,2023-05-04 12:22:00+00:00,161.82,161.9,161.82,161.9,161.857,5
|
||||
263,2023-05-04 12:23:00+00:00,161.92,161.95,161.92,161.95,161.949,4
|
||||
264,2023-05-04 12:24:00+00:00,161.93,161.95,161.85,161.95,161.93,11
|
||||
265,2023-05-04 12:25:00+00:00,161.95,162.0,161.91,161.91,161.969,10
|
||||
266,2023-05-04 12:26:00+00:00,161.93,162.03,161.93,162.03,161.983,15
|
||||
267,2023-05-04 12:27:00+00:00,162.03,162.05,161.93,162.05,162.04,13
|
||||
268,2023-05-04 12:28:00+00:00,162.05,162.07,162.0,162.0,162.047,17
|
||||
269,2023-05-04 12:29:00+00:00,162.07,162.07,161.93,161.95,162.005,8
|
||||
270,2023-05-04 12:30:00+00:00,161.95,162.2,161.8,162.04,161.984,40
|
||||
271,2023-05-04 12:31:00+00:00,162.11,162.16,162.1,162.11,162.122,12
|
||||
272,2023-05-04 12:32:00+00:00,162.04,162.56,162.04,162.53,162.348,60
|
||||
273,2023-05-04 12:33:00+00:00,162.5,162.58,162.49,162.5,162.538,46
|
||||
274,2023-05-04 12:34:00+00:00,162.41,162.41,162.2,162.31,162.23,29
|
||||
275,2023-05-04 12:35:00+00:00,162.24,162.3,162.12,162.3,162.201,15
|
||||
276,2023-05-04 12:36:00+00:00,162.39,162.52,162.34,162.35,162.394,15
|
||||
277,2023-05-04 12:37:00+00:00,162.39,162.5,162.35,162.5,162.424,25
|
||||
278,2023-05-04 12:38:00+00:00,162.53,162.97,162.52,162.96,162.71,65
|
||||
279,2023-05-04 12:39:00+00:00,162.96,162.99,162.71,162.78,162.885,63
|
||||
280,2023-05-04 12:40:00+00:00,162.72,162.78,162.65,162.78,162.719,34
|
||||
281,2023-05-04 12:41:00+00:00,162.75,162.9,162.72,162.8,162.778,21
|
||||
282,2023-05-04 12:42:00+00:00,162.8,162.8,162.63,162.63,162.672,23
|
||||
283,2023-05-04 12:43:00+00:00,162.63,162.78,162.61,162.78,162.685,18
|
||||
284,2023-05-04 12:44:00+00:00,162.68,162.74,162.65,162.7,162.7,10
|
||||
285,2023-05-04 12:45:00+00:00,162.66,162.66,162.52,162.52,162.581,17
|
||||
286,2023-05-04 12:46:00+00:00,162.51,162.61,162.51,162.61,162.537,14
|
||||
287,2023-05-04 12:47:00+00:00,162.69,162.69,162.6,162.67,162.666,7
|
||||
288,2023-05-04 12:48:00+00:00,162.65,162.68,162.51,162.53,162.605,20
|
||||
289,2023-05-04 12:49:00+00:00,162.55,162.65,162.55,162.65,162.578,14
|
||||
290,2023-05-04 12:50:00+00:00,162.65,162.7,162.62,162.7,162.656,15
|
||||
291,2023-05-04 12:51:00+00:00,162.55,162.72,162.55,162.72,162.661,12
|
||||
292,2023-05-04 12:52:00+00:00,162.7,162.7,162.59,162.59,162.658,5
|
||||
293,2023-05-04 12:53:00+00:00,162.65,162.91,162.64,162.82,162.789,44
|
||||
294,2023-05-04 12:54:00+00:00,162.89,163.0,162.82,162.86,162.953,31
|
||||
295,2023-05-04 12:55:00+00:00,162.86,163.0,162.86,163.0,162.938,21
|
||||
296,2023-05-04 12:56:00+00:00,162.95,163.0,162.86,162.96,162.936,66
|
||||
297,2023-05-04 12:57:00+00:00,162.85,162.92,162.82,162.9,162.862,16
|
||||
298,2023-05-04 12:58:00+00:00,162.82,162.9,162.82,162.85,162.87,9
|
||||
299,2023-05-04 12:59:00+00:00,162.85,162.86,162.7,162.72,162.796,37
|
||||
300,2023-05-04 13:00:00+00:00,162.76,162.88,162.75,162.88,162.832,25
|
||||
301,2023-05-04 13:01:00+00:00,162.92,163.0,162.9,162.91,162.989,76
|
||||
302,2023-05-04 13:02:00+00:00,162.92,162.94,162.82,162.93,162.871,13
|
||||
303,2023-05-04 13:03:00+00:00,162.92,162.96,162.8,162.8,162.879,19
|
||||
304,2023-05-04 13:04:00+00:00,162.8,162.85,162.78,162.8,162.799,14
|
||||
305,2023-05-04 13:05:00+00:00,162.85,162.88,162.82,162.87,162.852,13
|
||||
306,2023-05-04 13:06:00+00:00,162.83,162.89,162.82,162.86,162.836,18
|
||||
307,2023-05-04 13:07:00+00:00,162.9,162.94,162.9,162.94,162.909,16
|
||||
308,2023-05-04 13:08:00+00:00,162.92,162.92,162.81,162.9,162.865,23
|
||||
309,2023-05-04 13:09:00+00:00,162.86,162.89,162.77,162.79,162.844,31
|
||||
310,2023-05-04 13:10:00+00:00,162.85,162.85,162.7,162.8,162.755,27
|
||||
311,2023-05-04 13:11:00+00:00,162.83,162.85,162.8,162.8,162.833,13
|
||||
312,2023-05-04 13:12:00+00:00,162.83,162.83,162.7,162.7,162.735,19
|
||||
313,2023-05-04 13:13:00+00:00,162.76,162.76,162.6,162.65,162.648,33
|
||||
314,2023-05-04 13:14:00+00:00,162.68,162.68,162.62,162.62,162.632,18
|
||||
315,2023-05-04 13:15:00+00:00,162.63,162.7,162.63,162.7,162.648,10
|
||||
316,2023-05-04 13:16:00+00:00,162.69,162.7,162.64,162.65,162.68,11
|
||||
317,2023-05-04 13:17:00+00:00,162.65,162.67,162.63,162.66,162.646,7
|
||||
318,2023-05-04 13:18:00+00:00,162.66,162.72,162.64,162.7,162.695,22
|
||||
319,2023-05-04 13:19:00+00:00,162.7,162.72,162.7,162.7,162.706,9
|
||||
320,2023-05-04 13:20:00+00:00,162.7,162.73,162.68,162.71,162.705,17
|
||||
321,2023-05-04 13:21:00+00:00,162.72,162.75,162.71,162.71,162.726,5
|
||||
322,2023-05-04 13:22:00+00:00,162.7,162.75,162.69,162.74,162.712,17
|
||||
323,2023-05-04 13:23:00+00:00,162.75,162.85,162.75,162.82,162.798,17
|
||||
324,2023-05-04 13:24:00+00:00,162.84,162.9,162.84,162.87,162.881,27
|
||||
325,2023-05-04 13:25:00+00:00,162.88,162.9,162.84,162.85,162.865,33
|
||||
326,2023-05-04 13:26:00+00:00,162.9,162.9,162.85,162.89,162.893,28
|
||||
327,2023-05-04 13:27:00+00:00,162.9,163.25,162.85,162.9,162.98,162
|
||||
328,2023-05-04 13:28:00+00:00,162.93,162.93,162.7,162.73,162.785,40
|
||||
329,2023-05-04 13:29:00+00:00,162.7,162.76,162.66,162.76,162.732,27
|
||||
330,2023-05-04 13:30:00+00:00,162.71,162.86,161.85,162.06,162.452,2253
|
||||
331,2023-05-04 13:31:00+00:00,162.06,162.08,161.24,161.38,161.667,1999
|
||||
332,2023-05-04 13:32:00+00:00,161.37,161.6,161.11,161.16,161.3,1508
|
||||
333,2023-05-04 13:33:00+00:00,161.21,162.1,161.12,161.76,161.82,1933
|
||||
334,2023-05-04 13:34:00+00:00,161.78,162.41,161.0,162.14,161.728,2267
|
||||
335,2023-05-04 13:35:00+00:00,162.13,162.5,161.8,161.81,162.184,1559
|
||||
336,2023-05-04 13:36:00+00:00,161.81,162.27,161.66,162.12,162.062,995
|
||||
337,2023-05-04 13:37:00+00:00,162.08,162.95,162.08,162.56,162.6,1988
|
||||
338,2023-05-04 13:38:00+00:00,162.56,162.72,162.25,162.37,162.473,1098
|
||||
339,2023-05-04 13:39:00+00:00,162.37,162.58,162.15,162.38,162.356,1022
|
||||
340,2023-05-04 13:40:00+00:00,162.39,162.39,161.56,161.71,161.938,1484
|
||||
341,2023-05-04 13:41:00+00:00,161.65,162.08,161.04,161.14,161.449,1801
|
||||
342,2023-05-04 13:42:00+00:00,161.1,161.49,160.96,161.27,161.219,1442
|
||||
343,2023-05-04 13:43:00+00:00,161.29,161.6,161.09,161.14,161.331,1294
|
||||
344,2023-05-04 13:44:00+00:00,161.16,161.38,161.01,161.34,161.165,1122
|
||||
345,2023-05-04 13:45:00+00:00,161.31,161.74,161.22,161.41,161.442,1284
|
||||
346,2023-05-04 13:46:00+00:00,161.42,161.65,161.27,161.51,161.489,853
|
||||
347,2023-05-04 13:47:00+00:00,161.53,161.59,161.0,161.47,161.274,1431
|
||||
348,2023-05-04 13:48:00+00:00,161.5,161.5,161.0,161.22,161.194,853
|
||||
349,2023-05-04 13:49:00+00:00,161.2,161.75,161.15,161.74,161.426,1305
|
||||
350,2023-05-04 13:50:00+00:00,161.74,162.26,161.66,162.16,162.023,1935
|
||||
351,2023-05-04 13:51:00+00:00,162.14,162.16,161.87,161.9,162.048,940
|
||||
352,2023-05-04 13:52:00+00:00,161.9,162.15,161.64,161.93,161.917,966
|
||||
353,2023-05-04 13:53:00+00:00,161.95,162.3,161.93,162.28,162.12,1011
|
||||
354,2023-05-04 13:54:00+00:00,162.3,162.45,162.05,162.2,162.237,969
|
||||
355,2023-05-04 13:55:00+00:00,162.17,162.5,162.1,162.46,162.287,873
|
||||
356,2023-05-04 13:56:00+00:00,162.46,162.49,162.2,162.27,162.354,760
|
||||
357,2023-05-04 13:57:00+00:00,162.27,162.38,162.08,162.18,162.191,690
|
||||
358,2023-05-04 13:58:00+00:00,162.18,162.29,161.65,161.75,162.004,1120
|
||||
359,2023-05-04 13:59:00+00:00,161.75,161.95,161.5,161.57,161.729,1006
|
||||
360,2023-05-04 14:00:00+00:00,161.56,161.72,161.38,161.52,161.573,1061
|
||||
361,2023-05-04 14:01:00+00:00,161.51,161.53,161.02,161.21,161.198,1367
|
||||
362,2023-05-04 14:02:00+00:00,161.22,161.34,161.1,161.16,161.213,872
|
||||
363,2023-05-04 14:03:00+00:00,161.16,161.16,160.56,160.76,160.916,1999
|
||||
364,2023-05-04 14:04:00+00:00,160.75,160.79,160.47,160.62,160.608,1688
|
||||
365,2023-05-04 14:05:00+00:00,160.61,160.82,160.5,160.61,160.659,1363
|
||||
366,2023-05-04 14:06:00+00:00,160.62,160.89,160.55,160.83,160.746,1186
|
||||
367,2023-05-04 14:07:00+00:00,160.81,160.9,160.54,160.61,160.711,1190
|
||||
368,2023-05-04 14:08:00+00:00,160.6,160.62,160.19,160.31,160.362,2017
|
||||
369,2023-05-04 14:09:00+00:00,160.32,160.59,160.21,160.27,160.361,1126
|
||||
370,2023-05-04 14:10:00+00:00,160.27,160.68,160.25,160.51,160.467,989
|
||||
371,2023-05-04 14:11:00+00:00,160.52,160.81,160.01,160.04,160.411,1656
|
||||
372,2023-05-04 14:12:00+00:00,160.03,160.29,160.0,160.13,160.087,1385
|
||||
373,2023-05-04 14:13:00+00:00,160.12,160.43,160.04,160.2,160.215,1045
|
||||
374,2023-05-04 14:14:00+00:00,160.18,160.69,160.03,160.52,160.382,1477
|
||||
375,2023-05-04 14:15:00+00:00,160.51,161.41,160.36,161.23,161.015,2559
|
||||
376,2023-05-04 14:16:00+00:00,161.21,161.22,160.63,160.7,160.864,1420
|
||||
377,2023-05-04 14:17:00+00:00,160.72,160.72,160.22,160.34,160.47,1350
|
||||
378,2023-05-04 14:18:00+00:00,160.37,161.14,160.32,160.89,160.791,1225
|
||||
379,2023-05-04 14:19:00+00:00,160.86,161.3,160.82,160.98,161.034,1097
|
||||
380,2023-05-04 14:20:00+00:00,161.0,161.17,160.77,160.8,160.928,854
|
||||
381,2023-05-04 14:21:00+00:00,160.8,160.81,160.4,160.44,160.604,883
|
||||
382,2023-05-04 14:22:00+00:00,160.44,160.59,160.21,160.38,160.348,818
|
||||
383,2023-05-04 14:23:00+00:00,160.38,160.44,160.01,160.28,160.208,1367
|
||||
384,2023-05-04 14:24:00+00:00,160.27,160.31,159.65,159.76,159.95,2545
|
||||
385,2023-05-04 14:25:00+00:00,159.76,160.24,159.7,160.09,159.978,1468
|
||||
386,2023-05-04 14:26:00+00:00,160.06,160.1,159.75,159.86,159.904,952
|
||||
387,2023-05-04 14:27:00+00:00,159.86,160.37,159.85,160.09,160.121,1214
|
||||
388,2023-05-04 14:28:00+00:00,160.11,160.29,160.01,160.21,160.142,657
|
||||
389,2023-05-04 14:29:00+00:00,160.22,160.36,160.12,160.24,160.229,640
|
||||
390,2023-05-04 14:30:00+00:00,160.24,160.47,160.03,160.35,160.272,1066
|
||||
391,2023-05-04 14:31:00+00:00,160.34,160.6,160.2,160.25,160.401,981
|
||||
392,2023-05-04 14:32:00+00:00,160.27,160.5,160.12,160.4,160.333,714
|
||||
393,2023-05-04 14:33:00+00:00,160.4,160.5,160.25,160.41,160.384,568
|
||||
394,2023-05-04 14:34:00+00:00,160.39,160.59,160.15,160.56,160.38,1095
|
||||
395,2023-05-04 14:35:00+00:00,160.54,160.76,160.37,160.63,160.591,1277
|
||||
396,2023-05-04 14:36:00+00:00,160.64,160.67,160.31,160.31,160.503,763
|
||||
397,2023-05-04 14:37:00+00:00,160.3,160.44,160.07,160.14,160.228,956
|
||||
398,2023-05-04 14:38:00+00:00,160.14,160.15,159.89,159.99,160.0,1120
|
||||
399,2023-05-04 14:39:00+00:00,159.96,160.19,159.82,159.99,160.021,1044
|
||||
400,2023-05-04 14:40:00+00:00,159.99,160.26,159.97,160.2,160.159,767
|
||||
401,2023-05-04 14:41:00+00:00,160.2,160.25,159.96,160.0,160.122,782
|
||||
402,2023-05-04 14:42:00+00:00,160.0,160.4,159.89,160.25,160.196,1053
|
||||
403,2023-05-04 14:43:00+00:00,160.25,160.29,160.01,160.13,160.158,650
|
||||
404,2023-05-04 14:44:00+00:00,160.13,160.37,160.05,160.11,160.193,627
|
||||
405,2023-05-04 14:45:00+00:00,160.1,160.46,160.1,160.28,160.291,773
|
||||
406,2023-05-04 14:46:00+00:00,160.27,161.05,160.22,160.88,160.681,1520
|
||||
407,2023-05-04 14:47:00+00:00,160.84,161.07,160.68,160.94,160.884,1109
|
||||
408,2023-05-04 14:48:00+00:00,160.92,161.07,160.76,160.83,160.907,851
|
||||
409,2023-05-04 14:49:00+00:00,160.85,160.95,160.73,160.78,160.838,689
|
||||
410,2023-05-04 14:50:00+00:00,160.77,161.04,160.7,160.82,160.897,680
|
||||
411,2023-05-04 14:51:00+00:00,160.85,161.03,160.8,160.91,160.92,625
|
||||
412,2023-05-04 14:52:00+00:00,160.89,161.28,160.86,160.98,161.055,1217
|
||||
413,2023-05-04 14:53:00+00:00,160.95,161.12,160.85,161.12,160.958,532
|
||||
414,2023-05-04 14:54:00+00:00,161.14,161.48,161.07,161.4,161.305,1133
|
||||
415,2023-05-04 14:55:00+00:00,161.38,161.74,161.33,161.58,161.606,1310
|
||||
416,2023-05-04 14:56:00+00:00,161.55,161.63,161.39,161.52,161.519,745
|
||||
417,2023-05-04 14:57:00+00:00,161.5,161.59,161.33,161.34,161.455,722
|
||||
418,2023-05-04 14:58:00+00:00,161.34,161.57,161.26,161.49,161.393,724
|
||||
419,2023-05-04 14:59:00+00:00,161.49,161.68,161.36,161.46,161.498,727
|
||||
420,2023-05-04 15:00:00+00:00,161.42,161.7,161.31,161.68,161.496,783
|
||||
421,2023-05-04 15:01:00+00:00,161.66,161.69,161.36,161.47,161.513,626
|
||||
422,2023-05-04 15:02:00+00:00,161.46,161.61,161.37,161.45,161.494,565
|
||||
423,2023-05-04 15:03:00+00:00,161.43,161.82,161.4,161.51,161.641,938
|
||||
424,2023-05-04 15:04:00+00:00,161.48,161.63,161.42,161.6,161.504,545
|
||||
425,2023-05-04 15:05:00+00:00,161.59,162.14,161.52,162.14,161.87,1590
|
||||
426,2023-05-04 15:06:00+00:00,162.13,162.23,161.92,161.95,162.083,1067
|
||||
427,2023-05-04 15:07:00+00:00,161.93,162.27,161.84,162.1,162.071,869
|
||||
428,2023-05-04 15:08:00+00:00,162.1,162.2,161.92,162.18,162.058,649
|
||||
429,2023-05-04 15:09:00+00:00,162.19,162.52,162.18,162.46,162.424,2050
|
||||
430,2023-05-04 15:10:00+00:00,162.47,162.51,162.17,162.23,162.38,922
|
||||
431,2023-05-04 15:11:00+00:00,162.22,162.35,162.13,162.15,162.234,591
|
||||
432,2023-05-04 15:12:00+00:00,162.14,162.28,162.05,162.14,162.16,545
|
||||
433,2023-05-04 15:13:00+00:00,162.18,162.46,162.12,162.33,162.334,641
|
||||
434,2023-05-04 15:14:00+00:00,162.38,162.64,162.35,162.44,162.504,1121
|
||||
435,2023-05-04 15:15:00+00:00,162.47,162.58,162.26,162.28,162.44,683
|
||||
|
BIN
examples/3_tick_data/tick_data.gif
Normal file
BIN
examples/3_tick_data/tick_data.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 712 KiB |
23
examples/3_tick_data/tick_data.py
Normal file
23
examples/3_tick_data/tick_data.py
Normal file
@ -0,0 +1,23 @@
|
||||
import pandas as pd
|
||||
from time import sleep
|
||||
from lightweight_charts import Chart
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
df1 = pd.read_csv('ohlc.csv')
|
||||
|
||||
# Columns: | time | price | volume (if volume is enabled) |
|
||||
df2 = pd.read_csv('ticks.csv')
|
||||
|
||||
chart = Chart(volume_enabled=False)
|
||||
|
||||
chart.set(df1)
|
||||
|
||||
chart.show()
|
||||
|
||||
for i, tick in df2.iterrows():
|
||||
chart.update_from_tick(tick)
|
||||
|
||||
sleep(0.03)
|
||||
|
||||
2466
examples/3_tick_data/ticks.csv
Normal file
2466
examples/3_tick_data/ticks.csv
Normal file
File diff suppressed because it is too large
Load Diff
BIN
examples/4_line_indicators/line_indicators.png
Normal file
BIN
examples/4_line_indicators/line_indicators.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 503 KiB |
26
examples/4_line_indicators/line_indicators.py
Normal file
26
examples/4_line_indicators/line_indicators.py
Normal file
@ -0,0 +1,26 @@
|
||||
import pandas as pd
|
||||
from lightweight_charts import Chart
|
||||
|
||||
|
||||
def calculate_sma(data: pd.DataFrame, period: int = 50):
|
||||
def avg(d: pd.DataFrame):
|
||||
return d['close'].mean()
|
||||
result = []
|
||||
for i in range(period - 1, len(data)):
|
||||
val = avg(data.iloc[i - period + 1:i])
|
||||
result.append({'time': data.iloc[i]['date'], 'value': val})
|
||||
return pd.DataFrame(result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
chart = Chart()
|
||||
|
||||
df = pd.read_csv('ohlcv.csv')
|
||||
chart.set(df)
|
||||
|
||||
line = chart.create_line()
|
||||
sma_data = calculate_sma(df)
|
||||
line.set(sma_data)
|
||||
|
||||
chart.show(block=True)
|
||||
2982
examples/4_line_indicators/ohlcv.csv
Normal file
2982
examples/4_line_indicators/ohlcv.csv
Normal file
File diff suppressed because it is too large
Load Diff
2982
examples/5_styling/ohlcv.csv
Normal file
2982
examples/5_styling/ohlcv.csv
Normal file
File diff suppressed because it is too large
Load Diff
BIN
examples/5_styling/styling.png
Normal file
BIN
examples/5_styling/styling.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 512 KiB |
26
examples/5_styling/styling.py
Normal file
26
examples/5_styling/styling.py
Normal file
@ -0,0 +1,26 @@
|
||||
import pandas as pd
|
||||
from lightweight_charts import Chart
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
chart = Chart(debug=True)
|
||||
|
||||
df = pd.read_csv('ohlcv.csv')
|
||||
|
||||
chart.layout(background_color='#090008', text_color='#FFFFFF', font_size=16, font_family='Helvetica')
|
||||
|
||||
chart.candle_style(up_color='#00ff55', down_color='#ed4807', border_up_color='#FFFFFF', border_down_color='#FFFFFF',
|
||||
wick_up_color='#FFFFFF', wick_down_color='#FFFFFF')
|
||||
|
||||
chart.volume_config(up_color='#00ff55', down_color='#ed4807')
|
||||
|
||||
chart.watermark('1D', color='rgba(180, 180, 240, 0.7)')
|
||||
|
||||
chart.crosshair(mode='normal', vert_color='#FFFFFF', vert_style='dotted', horz_color='#FFFFFF', horz_style='dotted')
|
||||
|
||||
chart.legend(visible=True, font_size=14)
|
||||
|
||||
chart.set(df)
|
||||
|
||||
chart.show(block=True)
|
||||
BIN
examples/6_callbacks/callbacks.gif
Normal file
BIN
examples/6_callbacks/callbacks.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 633 KiB |
18
examples/6_callbacks/callbacks.py
Normal file
18
examples/6_callbacks/callbacks.py
Normal file
@ -0,0 +1,18 @@
|
||||
import pandas as pd
|
||||
from lightweight_charts import Chart
|
||||
|
||||
|
||||
def on_click(bar: dict):
|
||||
print(f"Time: {bar['time']} | Close: {bar['close']}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
chart = Chart()
|
||||
|
||||
df = pd.read_csv('ohlcv.csv')
|
||||
chart.set(df)
|
||||
|
||||
chart.subscribe_click(on_click)
|
||||
|
||||
chart.show(block=True)
|
||||
2982
examples/6_callbacks/ohlcv.csv
Normal file
2982
examples/6_callbacks/ohlcv.csv
Normal file
File diff suppressed because it is too large
Load Diff
8
lightweight_charts/__init__.py
Normal file
8
lightweight_charts/__init__.py
Normal file
@ -0,0 +1,8 @@
|
||||
from .chart import Chart
|
||||
from .js import LWC
|
||||
|
||||
try:
|
||||
import wx.html2
|
||||
from .widgets import WxChart
|
||||
except:
|
||||
pass
|
||||
210
lightweight_charts/chart.py
Normal file
210
lightweight_charts/chart.py
Normal file
@ -0,0 +1,210 @@
|
||||
|
||||
import pandas as pd
|
||||
import multiprocessing as mp
|
||||
from uuid import UUID
|
||||
from datetime import datetime
|
||||
from typing import Union
|
||||
|
||||
from lightweight_charts.pywebview import _loop
|
||||
from lightweight_charts.util import LINE_TYPE, POSITION, SHAPE, CROSSHAIR_MODE, PRICE_SCALE_MODE
|
||||
|
||||
|
||||
class Line:
|
||||
def __init__(self, chart, line_id):
|
||||
self._chart = chart
|
||||
self.id = line_id
|
||||
|
||||
def set(self, data: pd.DataFrame):
|
||||
self._chart._go('_set_line_data', self.id, data)
|
||||
|
||||
def update(self, series: pd.Series):
|
||||
"""
|
||||
Updates the line data.\n
|
||||
:param series: columns: date/time, price
|
||||
"""
|
||||
self._chart._go('_update_line_data', self.id, series)
|
||||
|
||||
|
||||
class Chart:
|
||||
def __init__(self, volume_enabled: bool = True, width: int = 800, height: int = 600, x: int = None, y: int = None,
|
||||
on_top: bool = False, debug: bool = False):
|
||||
self.debug = debug
|
||||
self.volume_enabled = volume_enabled
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.on_top = on_top
|
||||
|
||||
self._q = mp.Queue()
|
||||
self._result_q = mp.Queue()
|
||||
self._exit = mp.Event()
|
||||
|
||||
try:
|
||||
mp.Process(target=_loop, args=(self,), daemon=True).start()
|
||||
except:
|
||||
pass
|
||||
|
||||
def _go(self, func, *args): self._q.put((func, args))
|
||||
|
||||
def _go_return(self, func, *args):
|
||||
self._q.put((func, args))
|
||||
return self._result_q.get()
|
||||
|
||||
def show(self, block: bool = False):
|
||||
"""
|
||||
Shows the chart window.\n
|
||||
:param block: blocks execution until the chart is closed.
|
||||
"""
|
||||
self._go('show')
|
||||
self._exit.wait() if block else None
|
||||
|
||||
def hide(self):
|
||||
self._go('hide')
|
||||
|
||||
def exit(self):
|
||||
self._go('exit')
|
||||
|
||||
def run_script(self, script: str):
|
||||
"""
|
||||
For advanced users; evaluates JavaScript within the Webview.
|
||||
"""
|
||||
self._go('run_script', script)
|
||||
|
||||
def set(self, data: pd.DataFrame):
|
||||
"""
|
||||
Sets the initial data for the chart.\n
|
||||
:param data: columns: date/time, open, high, low, close, volume (if volume enabled).
|
||||
"""
|
||||
self._go('set', data)
|
||||
|
||||
def update(self, series: pd.Series):
|
||||
"""
|
||||
Updates the data from a bar;
|
||||
if series['time'] is the same time as the last bar, the last bar will be overwritten.\n
|
||||
:param series: columns: date/time, open, high, low, close, volume (if volume enabled).
|
||||
"""
|
||||
self._go('update', series)
|
||||
|
||||
def update_from_tick(self, series: pd.Series):
|
||||
"""
|
||||
Updates the data from a tick.\n
|
||||
:param series: columns: date/time, price, volume (if volume enabled).
|
||||
"""
|
||||
self._go('update_from_tick', series)
|
||||
|
||||
def create_line(self, color: str = 'rgba(214, 237, 255, 0.6)', width: int = 2):
|
||||
"""
|
||||
Creates and returns a Line object.)\n
|
||||
:return a Line object used to set/update the line.
|
||||
"""
|
||||
line_id = self._go_return('create_line', color, width)
|
||||
return Line(self, line_id)
|
||||
|
||||
def marker(self, time: datetime = None, position: POSITION = 'below', shape: SHAPE = 'arrow_up',
|
||||
color='#2196F3', text='') -> UUID:
|
||||
"""
|
||||
Creates a new marker.\n
|
||||
:param time: The time that the marker will be placed at. If no time is given, it will be placed at the last bar.
|
||||
:param position: The position of the marker.
|
||||
:param color: The color of the marker (rgb, rgba or hex).
|
||||
:param shape: The shape of the marker.
|
||||
:param text: The text to be placed with the marker.
|
||||
:return: The UUID of the marker placed.
|
||||
"""
|
||||
return self._go_return('marker', time, position, shape, color, text)
|
||||
|
||||
def remove_marker(self, marker_id: UUID):
|
||||
"""
|
||||
Removes the marker with the given uuid.\n
|
||||
:param marker_id:
|
||||
"""
|
||||
self._go('remove_marker', marker_id)
|
||||
|
||||
def horizontal_line(self, price: Union[float, int], color: str = 'rgb(122, 146, 202)', width: int = 1,
|
||||
style: LINE_TYPE = 'solid', text: str = '', axis_label_visible: bool = True):
|
||||
"""
|
||||
Creates a horizontal line at the given price.\n
|
||||
"""
|
||||
self._go('horizontal_line', price, color, width, style, text, axis_label_visible)
|
||||
|
||||
def remove_horizontal_line(self, price: Union[float, int]):
|
||||
"""
|
||||
Removes a horizontal line at the given price.
|
||||
"""
|
||||
self._go('remove_horizontal_line', price)
|
||||
|
||||
def config(self, mode: PRICE_SCALE_MODE = None, title: str = None, right_padding: float = None):
|
||||
"""
|
||||
:param mode: Chart price scale mode.
|
||||
:param title: Last price label text.
|
||||
:param right_padding: How many bars of empty space to the right of the last bar.
|
||||
"""
|
||||
self._go('config', mode, title, right_padding)
|
||||
|
||||
def time_scale(self, time_visible: bool = True, seconds_visible: bool = False):
|
||||
"""
|
||||
Options for the time scale of the chart.
|
||||
:param time_visible: Time visibility control.
|
||||
:param seconds_visible: Seconds visibility control
|
||||
:return:
|
||||
"""
|
||||
self._go('time_scale', time_visible, seconds_visible)
|
||||
|
||||
def layout(self, background_color: str = None, text_color: str = None, font_size: int = None,
|
||||
font_family: str = None):
|
||||
"""
|
||||
Global layout options for the chart.
|
||||
"""
|
||||
self._go('layout', background_color, text_color, font_size, font_family)
|
||||
|
||||
def candle_style(self, up_color: str = 'rgba(39, 157, 130, 100)', down_color: str = 'rgba(200, 97, 100, 100)',
|
||||
wick_enabled: bool = True, border_enabled: bool = True, border_up_color: str = '',
|
||||
border_down_color: str = '', wick_up_color: str = '', wick_down_color: str = ''):
|
||||
"""
|
||||
Candle styling for each of its parts.
|
||||
"""
|
||||
self._go('candle_style', up_color, down_color, wick_enabled, border_enabled,
|
||||
border_up_color, border_down_color, wick_up_color, wick_down_color)
|
||||
|
||||
def volume_config(self, scale_margin_top: float = 0.8, scale_margin_bottom: float = 0.0,
|
||||
up_color='rgba(83,141,131,0.8)', down_color='rgba(200,127,130,0.8)'):
|
||||
"""
|
||||
Configure volume settings.\n
|
||||
Numbers for scaling must be greater than 0 and less than 1.\n
|
||||
Volume colors must be applied prior to setting/updating the bars.\n
|
||||
:param scale_margin_top: Scale the top of the margin.
|
||||
:param scale_margin_bottom: Scale the bottom of the margin.
|
||||
:param up_color: Volume color for upward direction (rgb, rgba or hex)
|
||||
:param down_color: Volume color for downward direction (rgb, rgba or hex)
|
||||
"""
|
||||
self._go('volume_config', scale_margin_top, scale_margin_bottom, up_color, down_color)
|
||||
|
||||
def crosshair(self, mode: CROSSHAIR_MODE = 'normal', vert_width: int = 1, vert_color: str = None,
|
||||
vert_style: LINE_TYPE = None, vert_label_background_color: str = None, horz_width: int = 1,
|
||||
horz_color: str = None, horz_style: LINE_TYPE = None, horz_label_background_color: str = None):
|
||||
"""
|
||||
Crosshair formatting for its vertical and horizontal axes.
|
||||
"""
|
||||
self._go('crosshair', mode, vert_width, vert_color, vert_style, vert_label_background_color,
|
||||
horz_width, horz_color, horz_style, horz_label_background_color)
|
||||
|
||||
def watermark(self, text: str, font_size: int = 44, color: str = 'rgba(180, 180, 200, 0.5)'):
|
||||
"""
|
||||
Adds a watermark to the chart.
|
||||
"""
|
||||
self._go('watermark', text, font_size, color)
|
||||
|
||||
def legend(self, visible: bool = False, ohlc: bool = True, percent: bool = True, color: str = None,
|
||||
font_size: int = None, font_family: str = None):
|
||||
"""
|
||||
Configures the legend of the chart.
|
||||
"""
|
||||
self._go('legend', visible, ohlc, percent, color, font_size, font_family)
|
||||
|
||||
def subscribe_click(self, function: object):
|
||||
"""
|
||||
Subscribes the given function to a chart 'click' event.
|
||||
The event returns a dictionary containing the bar object at the time clicked.
|
||||
"""
|
||||
self._go('subscribe_click', function)
|
||||
634
lightweight_charts/js.py
Normal file
634
lightweight_charts/js.py
Normal file
@ -0,0 +1,634 @@
|
||||
import pandas as pd
|
||||
import uuid
|
||||
from datetime import timedelta, datetime
|
||||
from typing import Dict, Union
|
||||
|
||||
from lightweight_charts.pkg import LWC_3_5_0
|
||||
from lightweight_charts.util import LINE_TYPE, POSITION, SHAPE, CROSSHAIR_MODE, _crosshair_mode, _line_type, \
|
||||
MissingColumn, _js_bool, _price_scale_mode, PRICE_SCALE_MODE, _position, _shape
|
||||
|
||||
|
||||
class Line:
|
||||
def __init__(self, lwc, line_id, color, width):
|
||||
self._lwc = lwc
|
||||
self.loaded = False
|
||||
self.id = line_id
|
||||
self.color = color
|
||||
self.width = width
|
||||
|
||||
def set(self, data: pd.DataFrame):
|
||||
self._lwc._set_line_data(self.id, data)
|
||||
|
||||
def update(self, series: pd.Series):
|
||||
"""
|
||||
Updates the line data.\n
|
||||
:param series: columns: date/time, price
|
||||
"""
|
||||
self._lwc._update_line_data(self.id, series)
|
||||
|
||||
|
||||
class LWC:
|
||||
def __init__(self, volume_enabled):
|
||||
self.js_queue = []
|
||||
self.loaded = False
|
||||
self._html = HTML
|
||||
|
||||
self.volume_enabled = volume_enabled
|
||||
self.last_bar = None
|
||||
self.interval = None
|
||||
self._lines: Dict[uuid.UUID, Line] = {}
|
||||
|
||||
self.background_color = '#000000'
|
||||
self.volume_up_color = 'rgba(83,141,131,0.8)'
|
||||
self.volume_down_color = 'rgba(200,127,130,0.8)'
|
||||
|
||||
def _on_js_load(self):
|
||||
pass
|
||||
|
||||
def _stored(self, func, *args, **kwargs):
|
||||
if self.loaded:
|
||||
return False
|
||||
self.js_queue.append((func, args, kwargs))
|
||||
return True
|
||||
|
||||
def _set_last_bar(self, bar: pd.Series):
|
||||
self.last_bar = bar
|
||||
|
||||
def _set_interval(self, df: pd.DataFrame):
|
||||
df['time'] = pd.to_datetime(df['time'])
|
||||
intervals = df['time'].diff()
|
||||
counts = intervals.value_counts()
|
||||
self.interval = counts.index[0]
|
||||
|
||||
def _df_datetime_format(self, df: pd.DataFrame):
|
||||
if 'date' in df.columns:
|
||||
df = df.rename(columns={'date': 'time'})
|
||||
self._set_interval(df)
|
||||
df['time'] = df['time'].apply(self._datetime_format)
|
||||
return df
|
||||
|
||||
def _series_datetime_format(self, series):
|
||||
if 'date' in series.keys():
|
||||
series = series.rename({'date': 'time'})
|
||||
series['time'] = self._datetime_format(series['time'])
|
||||
return series
|
||||
|
||||
def _datetime_format(self, string):
|
||||
string = pd.to_datetime(string)
|
||||
if self.interval != timedelta(days=1):
|
||||
string = string.timestamp()
|
||||
string = self.interval.total_seconds() * (string // self.interval.total_seconds())
|
||||
else:
|
||||
string = string.strftime('%Y-%m-%d')
|
||||
return string
|
||||
|
||||
def run_script(self, script):
|
||||
pass
|
||||
|
||||
def set(self, df: pd.DataFrame):
|
||||
"""
|
||||
Sets the initial data for the chart.\n
|
||||
:param df: columns: date/time, open, high, low, close, volume (if volume enabled).
|
||||
"""
|
||||
if self._stored('set', df):
|
||||
return None
|
||||
|
||||
df = self._df_datetime_format(df)
|
||||
self._set_last_bar(df.iloc[-1])
|
||||
bars = df
|
||||
if self.volume_enabled:
|
||||
if 'volume' not in df:
|
||||
raise MissingColumn("Volume enabled, but 'volume' column was not found.")
|
||||
|
||||
volume = df.drop(columns=['open', 'high', 'low', 'close'])
|
||||
volume = volume.rename(columns={'volume': 'value'})
|
||||
volume['color'] = self.volume_down_color
|
||||
volume.loc[df['close'] > df['open'], 'color'] = self.volume_up_color
|
||||
|
||||
self.run_script(f'chart.volumeSeries.setData({volume.to_dict(orient="records")})')
|
||||
bars = df.drop(columns=['volume'])
|
||||
|
||||
bars = bars.to_dict(orient='records')
|
||||
self.run_script(f'chart.series.setData({bars})')
|
||||
|
||||
def update(self, series, from_tick=False):
|
||||
"""
|
||||
Updates the data from a bar;
|
||||
if series['time'] is the same time as the last bar, the last bar will be overwritten.\n
|
||||
:param series: columns: date/time, open, high, low, close, volume (if volume enabled).
|
||||
"""
|
||||
if self._stored('update', series, from_tick):
|
||||
return None
|
||||
|
||||
series = self._series_datetime_format(series) if not from_tick else series
|
||||
self._set_last_bar(series)
|
||||
if self.volume_enabled:
|
||||
if 'volume' not in series:
|
||||
raise MissingColumn("Volume enabled, but 'volume' column was not found.")
|
||||
|
||||
volume = series.drop(['open', 'high', 'low', 'close'])
|
||||
volume = volume.rename({'volume': 'value'})
|
||||
volume['color'] = self.volume_up_color if series['close'] > series['open'] else self.volume_down_color
|
||||
self.run_script(f'chart.volumeSeries.update({volume.to_dict()})')
|
||||
series = series.drop(['volume'])
|
||||
|
||||
|
||||
dictionary = series.to_dict()
|
||||
self.run_script(f'chart.series.update({dictionary})')
|
||||
|
||||
def update_from_tick(self, series):
|
||||
"""
|
||||
Updates the data from a tick.\n
|
||||
:param series: columns: date/time, price, volume (if volume enabled).
|
||||
"""
|
||||
if self._stored('update_from_tick', series):
|
||||
return None
|
||||
|
||||
series = self._series_datetime_format(series)
|
||||
bar = pd.Series()
|
||||
if series['time'] == self.last_bar['time']:
|
||||
bar = self.last_bar
|
||||
bar['high'] = max(self.last_bar['high'], series['price'])
|
||||
bar['low'] = min(self.last_bar['low'], series['price'])
|
||||
bar['close'] = series['price']
|
||||
if self.volume_enabled:
|
||||
if 'volume' not in series:
|
||||
raise MissingColumn("Volume enabled, but 'volume' column was not found.")
|
||||
bar['volume'] = series['volume']
|
||||
else:
|
||||
for key in ('open', 'high', 'low', 'close'):
|
||||
bar[key] = series['price']
|
||||
bar['time'] = series['time']
|
||||
bar['volume'] = 0
|
||||
self.update(bar, from_tick=True)
|
||||
|
||||
def create_line(self, color: str = 'rgba(214, 237, 255, 0.6)', width: int = 2):
|
||||
"""
|
||||
Creates and returns a Line object.)\n
|
||||
:return a Line object used to set/update the line.
|
||||
"""
|
||||
line_id = uuid.uuid4()
|
||||
self._lines[line_id] = Line(self, line_id, color, width)
|
||||
return self._lines[line_id]
|
||||
|
||||
def _set_line_data(self, line_id, df: pd.DataFrame):
|
||||
if self._stored('_set_line_data', line_id, df):
|
||||
return None
|
||||
|
||||
line = self._lines[line_id]
|
||||
if not line.loaded:
|
||||
self.run_script(f'''
|
||||
let lineSeries = {{
|
||||
color: '{line.color}',
|
||||
lineWidth: {line.width},
|
||||
}};
|
||||
let line = {{
|
||||
series: chart.chart.addLineSeries(lineSeries),
|
||||
id: '{line_id}',
|
||||
}};
|
||||
lines.push(line)
|
||||
''')
|
||||
line.loaded = True
|
||||
df = self._df_datetime_format(df)
|
||||
self.run_script(f'''
|
||||
lines.forEach(function (line) {{
|
||||
if ('{line_id}' === line.id) {{
|
||||
line.series.setData({df.to_dict('records')})
|
||||
}}
|
||||
}})''')
|
||||
|
||||
def _update_line_data(self, line_id, series: pd.Series):
|
||||
if self._stored('_update_line_data', line_id, series):
|
||||
return None
|
||||
|
||||
series = self._series_datetime_format(series)
|
||||
self.run_script(f'''
|
||||
lines.forEach(function (line) {{
|
||||
if ('{line_id}' === line.id) {{
|
||||
line.series.update({series.to_dict()})
|
||||
}}
|
||||
}})''')
|
||||
|
||||
def marker(self, time: datetime = None, position: POSITION = 'below', shape: SHAPE = 'arrow_up',
|
||||
color: str = '#2196F3', text: str = '', m_id: uuid.UUID = None) -> uuid.UUID:
|
||||
"""
|
||||
Creates a new marker.\n
|
||||
:param time: The time that the marker will be placed at. If no time is given, it will be placed at the last bar.
|
||||
:param position: The position of the marker.
|
||||
:param color: The color of the marker (rgb, rgba or hex).
|
||||
:param shape: The shape of the marker.
|
||||
:param text: The text to be placed with the marker.
|
||||
:return: The UUID of the marker placed.
|
||||
"""
|
||||
if not m_id:
|
||||
m_id = uuid.uuid4()
|
||||
if self._stored('marker', time, position, shape, color, text, m_id):
|
||||
return m_id
|
||||
|
||||
time = self.last_bar['time'] if not time else self._datetime_format(time)
|
||||
|
||||
self.run_script(f"""
|
||||
markers.push({{
|
||||
time: '{time}',
|
||||
position: '{_position(position)}',
|
||||
color: '{color}', shape: '{_shape(shape)}',
|
||||
text: '{text}',
|
||||
id: '{m_id}'
|
||||
}});
|
||||
chart.series.setMarkers(markers)""")
|
||||
return m_id
|
||||
|
||||
def remove_marker(self, m_id: uuid.UUID):
|
||||
"""
|
||||
Removes the marker with the given uuid.\n
|
||||
"""
|
||||
if self._stored('remove_marker', m_id):
|
||||
return None
|
||||
|
||||
self.run_script(f'''
|
||||
markers.forEach(function (marker) {{
|
||||
if ('{m_id}' === marker.id) {{
|
||||
markers.splice(markers.indexOf(marker), 1)
|
||||
chart.series.setMarkers(markers)
|
||||
}}
|
||||
}});''')
|
||||
|
||||
def horizontal_line(self, price: Union[float, int], color: str = 'rgb(122, 146, 202)', width: int = 1,
|
||||
style: LINE_TYPE = 'solid', text: str = '', axis_label_visible=True):
|
||||
"""
|
||||
Creates a horizontal line at the given price.\n
|
||||
"""
|
||||
if self._stored('horizontal_line', price, color, width, style, text, axis_label_visible):
|
||||
return None
|
||||
|
||||
self.run_script(f"""
|
||||
let priceLine = {{
|
||||
price: {price},
|
||||
color: '{color}',
|
||||
lineWidth: {width},
|
||||
lineStyle: LightweightCharts.LineStyle.{style},
|
||||
axisLabelVisible: {'true' if axis_label_visible else 'false'},
|
||||
title: '{text}',
|
||||
}};
|
||||
let line = {{
|
||||
line: chart.series.createPriceLine(priceLine),
|
||||
price: {price},
|
||||
}};
|
||||
horizontal_lines.push(line)""")
|
||||
|
||||
def remove_horizontal_line(self, price: Union[float, int]):
|
||||
"""
|
||||
Removes a horizontal line at the given price.
|
||||
"""
|
||||
if self._stored('remove_horizontal_line', price):
|
||||
return None
|
||||
|
||||
self.run_script(f'''
|
||||
horizontal_lines.forEach(function (line) {{
|
||||
if ({price} === line.price) {{
|
||||
chart.series.removePriceLine(line.line);
|
||||
horizontal_lines.splice(horizontal_lines.indexOf(line), 1)
|
||||
}}
|
||||
}});''')
|
||||
|
||||
def config(self, mode: PRICE_SCALE_MODE = None, title: str = None, right_padding: float = None):
|
||||
"""
|
||||
:param mode: Chart price scale mode.
|
||||
:param title: Last price label text.
|
||||
:param right_padding: How many bars of empty space to the right of the last bar.
|
||||
"""
|
||||
if self._stored('config', mode, title, right_padding):
|
||||
return None
|
||||
|
||||
self.run_script(f'chart.chart.timeScale().scrollToPosition({right_padding}, false)') if right_padding else None
|
||||
self.run_script(f'chart.series.applyOptions({{title: "{title}"}})') if title else None
|
||||
self.run_script(
|
||||
f"chart.chart.priceScale().applyOptions({{mode: LightweightCharts.PriceScaleMode.{_price_scale_mode(mode)}}})") if mode else None
|
||||
|
||||
def time_scale(self, time_visible: bool = True, seconds_visible: bool = False):
|
||||
"""
|
||||
Options for the time scale of the chart.
|
||||
:param time_visible: Time visibility control.
|
||||
:param seconds_visible: Seconds visibility control
|
||||
:return:
|
||||
"""
|
||||
if self._stored('time_scale', time_visible, seconds_visible):
|
||||
return None
|
||||
|
||||
time = f'timeVisible: {_js_bool(time_visible)},'
|
||||
seconds = f'secondsVisible: {_js_bool(seconds_visible)}'
|
||||
self.run_script(f'''
|
||||
chart.chart.applyOptions({{
|
||||
timeScale: {{
|
||||
{time if time_visible is not None else ''}
|
||||
{seconds if seconds_visible is not None else ''}
|
||||
}}
|
||||
}})''')
|
||||
|
||||
def layout(self, background_color: str = None, text_color: str = None, font_size: int = None,
|
||||
font_family: str = None):
|
||||
"""
|
||||
Global layout options for the chart.
|
||||
"""
|
||||
if self._stored('layout', background_color, text_color, font_size, font_family):
|
||||
return None
|
||||
|
||||
self.background_color = background_color if background_color else self.background_color
|
||||
args = f"'{self.background_color}'", f"'{text_color}'", f"{font_size}", f"'{font_family}'",
|
||||
for key, arg in zip(('backgroundColor', 'textColor', 'fontSize', 'fontFamily'), args):
|
||||
if not arg:
|
||||
continue
|
||||
self.run_script(f"""
|
||||
chart.chart.applyOptions({{
|
||||
layout: {{
|
||||
{key}: {arg}
|
||||
}}
|
||||
}})""")
|
||||
|
||||
def candle_style(self, up_color: str = 'rgba(39, 157, 130, 100)', down_color: str = 'rgba(200, 97, 100, 100)',
|
||||
wick_enabled: bool = True, border_enabled: bool = True, border_up_color: str = '',
|
||||
border_down_color: str = '', wick_up_color: str = '', wick_down_color: str = ''):
|
||||
"""
|
||||
Candle styling for each of its parts.
|
||||
"""
|
||||
if self._stored('candle_style', up_color, down_color, wick_enabled, border_enabled,
|
||||
border_up_color, border_down_color, wick_up_color, wick_down_color):
|
||||
return None
|
||||
|
||||
params = None, 'upColor', 'downColor', 'wickVisible', 'borderVisible', 'borderUpColor', 'borderDownColor',\
|
||||
'wickUpColor', 'wickDownColor'
|
||||
for param, key_arg in zip(params, locals().items()):
|
||||
key, arg = key_arg
|
||||
if isinstance(arg, bool):
|
||||
arg = _js_bool(arg)
|
||||
if key == 'self' or arg is None:
|
||||
continue
|
||||
else:
|
||||
arg = f"'{arg}'"
|
||||
self.run_script(
|
||||
f"""chart.series.applyOptions({{
|
||||
{param}: {arg},
|
||||
}})""")
|
||||
|
||||
def volume_config(self, scale_margin_top: float = 0.8, scale_margin_bottom: float = 0.0,
|
||||
up_color='rgba(83,141,131,0.8)', down_color='rgba(200,127,130,0.8)'):
|
||||
"""
|
||||
Configure volume settings.\n
|
||||
Numbers for scaling must be greater than 0 and less than 1.\n
|
||||
Volume colors must be applied prior to setting/updating the bars.\n
|
||||
:param scale_margin_top: Scale the top of the margin.
|
||||
:param scale_margin_bottom: Scale the bottom of the margin.
|
||||
:param up_color: Volume color for upward direction (rgb, rgba or hex)
|
||||
:param down_color: Volume color for downward direction (rgb, rgba or hex)
|
||||
"""
|
||||
if self._stored('volume_config', scale_margin_top, scale_margin_bottom, up_color, down_color):
|
||||
return None
|
||||
|
||||
self.volume_up_color = up_color if up_color else self.volume_up_color
|
||||
self.volume_down_color = down_color if down_color else self.volume_down_color
|
||||
top = f'top: {scale_margin_top},'
|
||||
bottom = f'bottom: {scale_margin_bottom},'
|
||||
self.run_script(f'''
|
||||
chart.volumeSeries.priceScale().applyOptions({{
|
||||
scaleMargins: {{
|
||||
{top if top else ''}
|
||||
{bottom if bottom else ''}
|
||||
}}
|
||||
}})''')
|
||||
|
||||
def crosshair(self, mode: CROSSHAIR_MODE = 'normal', vert_width: int = 1, vert_color: str = None,
|
||||
vert_style: LINE_TYPE = None, vert_label_background_color: str = None, horz_width: int = 1,
|
||||
horz_color: str = None, horz_style: LINE_TYPE = None, horz_label_background_color: str = None):
|
||||
"""
|
||||
Crosshair formatting for its vertical and horizontal axes.
|
||||
"""
|
||||
if self._stored('crosshair', mode, vert_width, vert_color, vert_style, vert_label_background_color,
|
||||
horz_width, horz_color, horz_style, horz_label_background_color):
|
||||
return None
|
||||
|
||||
args = f"LightweightCharts.CrosshairMode.{_crosshair_mode(mode)}", \
|
||||
f"{vert_width}}}", f"'{vert_color}'}}", f"LightweightCharts.LineStyle.{_line_type(vert_style)}}}",\
|
||||
f"'{vert_label_background_color}'}}", \
|
||||
f"{horz_width}}}", f"'{horz_color}'}}", f"LightweightCharts.LineStyle.{_line_type(horz_style)}}}",\
|
||||
f"'{horz_label_background_color}'}}"
|
||||
|
||||
for key, arg in zip(
|
||||
('mode', 'vertLine: {width', 'vertLine: {color', 'vertLine: {style', 'vertLine: {labelBackgroundColor',
|
||||
'horzLine: {width', 'horzLine: {color', 'horzLine: {style', 'horzLine: {labelBackgroundColor'), args):
|
||||
if 'None' in arg:
|
||||
continue
|
||||
self.run_script(f'''
|
||||
chart.chart.applyOptions({{
|
||||
crosshair: {{
|
||||
{key}: {arg}
|
||||
}}}})''')
|
||||
|
||||
def watermark(self, text: str, font_size: int = 44, color: str = 'rgba(180, 180, 200, 0.5)'):
|
||||
"""
|
||||
Adds a watermark to the chart.
|
||||
"""
|
||||
if self._stored('watermark', text, font_size, color):
|
||||
return None
|
||||
|
||||
self.run_script(f'''
|
||||
chart.chart.applyOptions({{
|
||||
watermark: {{
|
||||
visible: true,
|
||||
fontSize: {font_size},
|
||||
horzAlign: 'center',
|
||||
vertAlign: 'center',
|
||||
color: '{color}',
|
||||
text: '{text}',
|
||||
}}
|
||||
}})''')
|
||||
|
||||
def legend(self, visible: bool = False, ohlc: bool = True, percent: bool = True, color: str = None,
|
||||
font_size: int = None, font_family: str = None):
|
||||
"""
|
||||
Configures the legend of the chart.
|
||||
"""
|
||||
if self._stored('legend', visible, ohlc, percent, color, font_size, font_family):
|
||||
return None
|
||||
|
||||
scripts = f'legendToggle = {_js_bool(visible)}; legend.innerText = ""', f'legendOHLCVisible = {_js_bool(ohlc)}',\
|
||||
f'legendPercentVisible = {_js_bool(percent)}', f'legend.style.color = {color}', \
|
||||
f'legend.style.fontSize = "{font_size}px"', f'legend.style.fontFamily = "{font_family}"'
|
||||
for script, arg in zip(scripts, (visible, ohlc, percent, color, font_size, font_family)):
|
||||
if arg is None:
|
||||
continue
|
||||
self.run_script(script)
|
||||
|
||||
|
||||
SCRIPT = """
|
||||
|
||||
const markers = []
|
||||
const horizontal_lines = []
|
||||
const lines = []
|
||||
|
||||
const up = 'rgba(39, 157, 130, 100)'
|
||||
const down = 'rgba(200, 97, 100, 100)'
|
||||
|
||||
|
||||
function makeChart(width, height, div) {
|
||||
return LightweightCharts.createChart(div, {
|
||||
width: width,
|
||||
height: height,
|
||||
layout: {
|
||||
textColor: '#d1d4dc',
|
||||
backgroundColor: '#000000',
|
||||
fontSize: 12,
|
||||
},
|
||||
rightPriceScale: {
|
||||
scaleMargins: {
|
||||
top: 0.3,
|
||||
bottom: 0.25,
|
||||
},
|
||||
},
|
||||
timeScale: {
|
||||
timeVisible: true,
|
||||
secondsVisible: false,
|
||||
},
|
||||
crosshair: {
|
||||
mode: LightweightCharts.CrosshairMode.Normal,
|
||||
},
|
||||
grid: {
|
||||
vertLines: {
|
||||
color: 'rgba(29, 30, 38, 5)',
|
||||
},
|
||||
horzLines: {
|
||||
color: 'rgba(29, 30, 58, 5)',
|
||||
},
|
||||
},
|
||||
handleScroll: {
|
||||
vertTouchDrag: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
function makeCandlestickSeries(chart){
|
||||
return chart.addCandlestickSeries({
|
||||
color: 'rgb(0, 120, 255)',
|
||||
upColor: up,
|
||||
borderUpColor: up,
|
||||
wickUpColor: up,
|
||||
|
||||
downColor: down,
|
||||
borderDownColor: down,
|
||||
wickDownColor: down,
|
||||
lineWidth: 2,
|
||||
})
|
||||
}
|
||||
|
||||
function makeVolumeSeries(chart) {
|
||||
return chart.addHistogramSeries({
|
||||
color: '#26a69a',
|
||||
priceFormat: {
|
||||
type: 'volume',
|
||||
},
|
||||
priceScaleId: '',
|
||||
});
|
||||
}
|
||||
|
||||
const chartsDiv = document.createElement('div')
|
||||
|
||||
chart.chart = makeChart(window.innerWidth, window.innerHeight, chartsDiv)
|
||||
chart.series = makeCandlestickSeries(chart.chart)
|
||||
chart.volumeSeries = makeVolumeSeries(chart.chart)
|
||||
|
||||
document.body.appendChild(chartsDiv)
|
||||
|
||||
const legend = document.createElement('div')
|
||||
legend.style.display = 'block'
|
||||
legend.style.position = 'absolute'
|
||||
legend.style.zIndex = 1000
|
||||
legend.style.width = '98vw'
|
||||
legend.style.top = '10px'
|
||||
legend.style.left = '10px'
|
||||
legend.style.fontFamily = 'Monaco'
|
||||
|
||||
legend.style.fontSize = '11px'
|
||||
legend.style.color = 'rgb(191, 195, 203)'
|
||||
|
||||
document.body.appendChild(legend)
|
||||
|
||||
chart.chart.priceScale('').applyOptions({
|
||||
scaleMargins: {
|
||||
top: 0.8,
|
||||
bottom: 0,
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('resize', function() {
|
||||
let width = window.innerWidth;
|
||||
let height = window.innerHeight;
|
||||
chart.chart.resize(width, height)
|
||||
});
|
||||
|
||||
function legendItemFormat(num) {
|
||||
return num.toFixed(2).toString().padStart(8, ' ')
|
||||
}
|
||||
let legendToggle = false
|
||||
let legendOHLCVisible = true
|
||||
let legendPercentVisible = true
|
||||
chart.chart.subscribeCrosshairMove((param) => {
|
||||
if (param.time){
|
||||
const data = param.seriesPrices.get(chart.series);
|
||||
if (!data || legendToggle === false) {return}
|
||||
const percentMove = ((data.close-data.open)/data.open)*100
|
||||
//legend.style.color = percentMove >= 0 ? up : down
|
||||
|
||||
let ohlc = `open: ${legendItemFormat(data.open)}
|
||||
| high: ${legendItemFormat(data.high)}
|
||||
| low: ${legendItemFormat(data.low)}
|
||||
| close: ${legendItemFormat(data.close)} `
|
||||
let percent = `| daily: ${percentMove >= 0 ? '+' : ''}${percentMove.toFixed(2)} %`
|
||||
|
||||
let finalString = ''
|
||||
if (legendOHLCVisible) {
|
||||
finalString += ohlc
|
||||
}
|
||||
if (legendPercentVisible) {
|
||||
finalString += percent
|
||||
}
|
||||
legend.innerHTML = finalString
|
||||
}
|
||||
else {
|
||||
legend.innerHTML = ''
|
||||
}
|
||||
});
|
||||
let isSubscribed = false
|
||||
function clickHandler(param) {
|
||||
if (!param.point || !isSubscribed) {return}
|
||||
let prices = param.seriesPrices.get(chart.series);
|
||||
let data = {
|
||||
time: param.time,
|
||||
open: prices.open,
|
||||
high: prices.high,
|
||||
low: prices.low,
|
||||
close: prices.close,
|
||||
}
|
||||
pywebview.api.onClick(data)
|
||||
|
||||
}
|
||||
chart.chart.subscribeClick(clickHandler)
|
||||
"""
|
||||
|
||||
HTML = f"""
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<title>lightweight-charts-python</title>
|
||||
<script>{LWC_3_5_0}</script>
|
||||
<meta name="viewport" content ="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body {{
|
||||
margin: 0;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="chart"></div>
|
||||
<script>
|
||||
{SCRIPT}
|
||||
</script>
|
||||
</body>
|
||||
</html>"""
|
||||
10
lightweight_charts/pkg.py
Normal file
10
lightweight_charts/pkg.py
Normal file
File diff suppressed because one or more lines are too long
79
lightweight_charts/pywebview.py
Normal file
79
lightweight_charts/pywebview.py
Normal file
@ -0,0 +1,79 @@
|
||||
import datetime
|
||||
|
||||
import webview
|
||||
from multiprocessing import Queue
|
||||
|
||||
from lightweight_charts.js import LWC
|
||||
|
||||
_q = Queue()
|
||||
_result_q = Queue()
|
||||
|
||||
DEBUG = True
|
||||
|
||||
|
||||
class API:
|
||||
def __init__(self):
|
||||
self.click_func = None
|
||||
|
||||
def onClick(self, data):
|
||||
if isinstance(data['time'], int):
|
||||
data['time'] = datetime.datetime.fromtimestamp(data['time'])
|
||||
else:
|
||||
data['time'] = datetime.datetime(data['time']['year'], data['time']['month'], data['time']['day'])
|
||||
self.click_func(data) if self.click_func else None
|
||||
|
||||
|
||||
class Webview(LWC):
|
||||
def __init__(self, chart):
|
||||
super().__init__(chart.volume_enabled)
|
||||
self.chart = chart
|
||||
self.started = False
|
||||
|
||||
self.js_api = API()
|
||||
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]
|
||||
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 subscribe_click(self, function):
|
||||
if self._stored('subscribe_click', function):
|
||||
return None
|
||||
|
||||
self.js_api.click_func = function
|
||||
self.run_script('isSubscribed = true')
|
||||
|
||||
def create_line(self, color: str = 'rgba(214, 237, 255, 0.6)', width: int = 2):
|
||||
return super().create_line(color, width).id
|
||||
|
||||
def hide(self): self.webview.hide()
|
||||
|
||||
def exit(self): self.webview.destroy()
|
||||
|
||||
|
||||
def _loop(chart, controller=None):
|
||||
wv = Webview(chart) if not controller else controller
|
||||
while 1:
|
||||
func, args = chart._q.get()
|
||||
try:
|
||||
result = getattr(wv, func)(*args)
|
||||
except KeyError as e:
|
||||
return
|
||||
if func == 'show':
|
||||
chart._exit.set()
|
||||
chart._result_q.put(result) if result is not None else None
|
||||
|
||||
61
lightweight_charts/util.py
Normal file
61
lightweight_charts/util.py
Normal file
@ -0,0 +1,61 @@
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class MissingColumn(KeyError):
|
||||
def __init__(self, message):
|
||||
super().__init__(message)
|
||||
self.msg = message
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.msg}'
|
||||
|
||||
|
||||
LINE_TYPE = Literal['solid', 'dotted', 'dashed', 'large_dashed', 'sparse_dotted']
|
||||
|
||||
POSITION = Literal['above', 'below', 'inside']
|
||||
|
||||
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 _position(p: 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
|
||||
28
lightweight_charts/widgets.py
Normal file
28
lightweight_charts/widgets.py
Normal file
@ -0,0 +1,28 @@
|
||||
from lightweight_charts.js import LWC
|
||||
try:
|
||||
import wx.html2
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class WxChart(LWC):
|
||||
def __init__(self, parent, width, height, volume_enabled=True):
|
||||
super().__init__(volume_enabled)
|
||||
self.webview = wx.html2.WebView.New(parent, size=(width, height))
|
||||
|
||||
self.webview.Bind(wx.html2.EVT_WEBVIEW_LOADED, self._on_js_load)
|
||||
self.webview.SetPage(self._html, '')
|
||||
|
||||
self.second_load = False
|
||||
|
||||
def run_script(self, script): self.webview.RunScript(script)
|
||||
|
||||
def _on_js_load(self, e: wx.html2.WebViewEvent):
|
||||
if not self.second_load:
|
||||
self.second_load = True
|
||||
return
|
||||
self.loaded = True
|
||||
for func, args, kwargs in self.js_queue:
|
||||
getattr(super(), func)(*args, **kwargs)
|
||||
|
||||
def get_webview(self): return self.webview
|
||||
15
setup.py
Normal file
15
setup.py
Normal file
@ -0,0 +1,15 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name='lightweight_charts',
|
||||
version='1.0.0',
|
||||
packages=find_packages(),
|
||||
install_requires=[
|
||||
'pandas',
|
||||
'pywebview',
|
||||
],
|
||||
# Additional package metadata
|
||||
author='louisnw01',
|
||||
description="Python framework for TradingView's Lightweight Charts JavaScript library.",
|
||||
url='https://github.com/SORT-THIS-OUT',
|
||||
)
|
||||
Reference in New Issue
Block a user