Compare commits

...

48 Commits

Author SHA1 Message Date
1c72818dae fixed 0 doesnt update in legend 2024-11-21 13:43:28 +01:00
92acc2b96a fix 2024-11-15 09:41:16 +01:00
ada85883c7 fix 2024-11-15 09:35:55 +01:00
3adf0b9ce3 fix 2024-11-15 09:33:50 +01:00
1551f6f904 fix 2024-11-15 09:23:35 +01:00
33c2d47858 fix 2024-11-15 09:21:52 +01:00
cab85bb4f8 fix 2024-11-15 09:18:40 +01:00
37af631a3e fix 2024-11-15 09:12:26 +01:00
f1024d551f fix 2024-11-15 09:04:27 +01:00
2b607f96be fix 2024-11-15 09:01:02 +01:00
81996a1891 fix 2024-11-15 08:43:46 +01:00
e526940717 fix 2024-11-15 08:33:39 +01:00
0e88137927 fix 2024-11-15 07:54:00 +01:00
5407e22bd6 fix 2024-11-15 07:46:43 +01:00
ef192e82f9 fix 2024-11-15 07:38:46 +01:00
1f4aa4fa8e fix 2024-11-15 07:25:08 +01:00
1deb397e28 fix 2024-11-15 07:10:41 +01:00
2c656fa640 fix 2024-11-15 07:00:58 +01:00
6393e618ce fix 2024-11-15 06:13:54 +01:00
b336857832 fix 2024-10-20 15:11:22 +02:00
e752ef8fdd fix 2024-10-20 14:55:37 +02:00
1c2afbf93b update 2024-10-17 09:28:38 +02:00
3f2a484cd7 fix 2024-10-13 15:39:54 +02:00
7b0acec3e6 added shorter syntax for one pane 2024-10-13 14:21:05 +02:00
a9cb8da66e readme udpate 2024-10-09 16:11:46 +02:00
9fca26db4b auto scale support 2024-10-09 16:06:19 +02:00
dfe1eafba9 multindex indicator support 2024-10-04 21:58:03 +02:00
8b9f3ad61f update main session marker 2024-10-04 13:52:32 +02:00
0da6839bcb examples moved to archive 2024-10-04 12:04:55 +02:00
c4356bef3a df and sr accessor enhancements 2024-10-04 11:54:24 +02:00
7986aa9195 update session marker 2024-10-03 16:54:52 +02:00
35f029714b bugifx 2024-10-02 13:41:14 +02:00
2b9f238a42 new version to setup 2024-09-27 14:22:26 +02:00
bbbd37520d bugfix date 2024-09-27 14:20:09 +02:00
51fc616f82 Update README.md 2024-08-06 22:54:06 +02:00
197936d69f Update README.md 2024-07-30 12:10:09 +02:00
789d27c54b Update README.md 2024-07-01 09:18:40 +02:00
f90e7789ab Update README.md 2024-06-27 14:23:40 +02:00
eb2c24e157 Merge branch 'main' of https://github.com/drew2323/lightweight-charts-python into main 2024-06-27 13:47:17 +02:00
c27401a59d Update README.md 2024-06-27 13:35:53 +02:00
da029fcbb7 Update README.md 2024-06-26 15:12:58 +02:00
ea2f53b794 Update README.md 2024-06-26 14:51:19 +02:00
19b1834a6d Update README.md 2024-06-26 14:50:20 +02:00
6de9ba9c33 Update README.md 2024-06-26 14:50:07 +02:00
105ea66731 Update README.md 2024-06-26 14:43:07 +02:00
9b8e43e1e8 Update README.md 2024-06-26 14:23:51 +02:00
9f9f22eb93 Update README.md 2024-06-26 14:21:43 +02:00
eb56a889a9 Update README.md 2024-06-26 09:09:52 +02:00
45 changed files with 727 additions and 83 deletions

137
README.md
View File

@ -1,17 +1,140 @@
Fork of lightweight-charts with enhancements and supporting proprietary workflow
* colored legends
* self picking colors
* support for left price scale
* support from pd series as input to set series
Fork of the original [lightweight-charts](louisnw01/lightweight-charts-python) with enhancements and supporting `vectorbtpro` workflow
* legend color matching each line color
* automatic color assignment if not provided
* support for multiple scales (right, left, middle1, middle2, histogram)
* accepts df,pd.series or `vectorbtpro indicator` object (including unpacking multi outputs)
* new markers_set method allowing to set pd.series or dataframe as markers input
* allows vbt indicators as input for set() method (extracting real values series from that)
* supports simple df/sr accessors `close.lw.plot()` for quick visualization of single panel chart
* df accessor unpacks dataframe columns and display them accordingly (ohlcv as ohlcv, vwap on the right, rsi on the left scale etc.)
* multipanes support `ch = chart([pane1, pane2], sync=True, title="Title", size="m")` to quickly display chart with N panes (`Panels`). Also supports syncing the Panels `sync=True` or using xloc.
<img width="1005" alt="image" src="https://github.com/drew2323/lightweight-charts-python/assets/28433232/856c32aa-e0ff-4de0-b4a2-befc34adb571">
## Instalation
Directly from the repository
```pip install git+https://github.com/drew2323/lightweight-charts-python.git```
## Examples
```python
from lightweight_charts import chart, Panel, PlotSRAccessor, PlotDFAccessor
#one liner, displays close series as line on single Panel
close_sr.lw.plot()
```
![alt text](image-2.png)
```python
close_sr.lw.plot(size="m") #on medium panesize
close_se.lw.plot(histogram=(trade_series, "trades")) #number of trades as histogram is displayed on top of that
```
![alt text](image-1.png)
```python
#one liner to display OHLCV
ohlcv_df.lw.plot() #if dataframe contains other columns they are displayed too - based on standard settings (rsi-on the left, vwap - right etc.)
```
![alt text](image-3.png)
```python
ohlcv_df.lw.plot(left=[(angle_series, "angle_momentum")]) #Another line is displayed on top of OHLCV on left scale
```
![alt text](image-4.png)
```python
ohlcv_complex_df.lw.plot() #df containing ohlcv and other columns
```
![alt text](image-5.png)
```python
t1data.ohlcv.data["BAC"].lw.plot(left=[(t1mom,"mom"),(t1mom_tt.mom.loc[:, (20,"1T")],),(t1mom_tt.mom.loc[:, (20,"5T")],)]) #display ohlcv 1m data along with 1min momentum ind and 2 multiindexed indicators on 1M and 5m resolution on 5M (calculated as multiindex)
#quickly plot vectorbtpro indicator on top of OHLCV data (with automatic unpacking)
macd = vbt.indicator("talib:MACD").run(t1data.data["BAC"].close)
t1data.ohlcv.data["BAC"].lw.plot(auto_scale=macd)
```
![alt text](image-6.png)
```python
#ONE PANEL - quick few liner, displays close series with label "close" on right pricescale and rsi on left price scale, all on single Panel
pane1 = Panel(
right=[(close, "close")],
left=[(rsi,"rsi")]
)
ch = chart([pane1])
##ONE PANEL - quicker
Panel(
auto_scale=[cdlbreakaway],
ohlcv=(t1data.ohlcv.data["BAC"],entries),
histogram=[],
right=[],
left=[],
middle1=[],
middle2=[]
).chart(size="s")
```
![alt text](image-7.png)
```python
# display two Panels
# on first displays ohlcv data, orderimbalance volume as histogram with opacity, bbands on the right pricescale and
# sma with short_signals and short_exits on the left pricescale
pane1 = Panel(
ohlcv=(t1data.data["BAC"],), #(series, entries, exits, other_markers)
histogram=[(order_imbalance_allvolume, "oivol",None, 0.3)], # [(series, name, "rgba(53, 94, 59, 0.6)", opacity)]
#following attributes assign lineseries series to different priceScaleIds, format: # [(series, name, entries, exits, other_markers)]
right=[(bbands)], #multioutput indicator, outputs are autom.extracted
left=[(sma, "sma", short_signals, short_exits) #simple vbt indicator with markers in and outs
(supertrend.trend, "ST_trend") #explicitly just one output of multioutput indicator
],
middle1=[],
middle2=[],
)
#on second panel displays also ohlcv data, and sma on the left pricescale and histogram
pane2 = Panel(
ohlcv=(t1data.data["BAC"],),
right=[],
left=[(sma, "sma_below", short_signals, short_exits)],
middle1=[],
middle2=[],
histogram=[(order_imbalance_sma, "oisma")],
)
#display both Panels, sync them and pick size, use xloc
ch = chart([pane1, pane2], sync=True, title="Title", size="l", xloc=slice("1-1-2024","1-2-2024"))
```
## Example with markers
```python
#assume i want to display simple entries or exits on series or ohlcv
#based on tuple positions it determines entries or exits (and set colors and shape accordingly)
pane1 = Panel(
ohlcv=(ohlcv_df, clean_long_entries, clean_short_entries)
)
ch = chart([pane1], title="Chart with Entry/Exit Markers", session=None, size="s")
```
<img width="798" alt="image" src="https://github.com/drew2323/lightweight-charts-python/assets/28433232/0cdc3930-b8b1-40f8-af95-55467879148b">
```python
#if you want to display more entries or exits, use tuples with their colors
# Create Panel with OHLC data and entry signals
pane1 = Panel(
ohlcv=(data.ohlcv.get(),
[(clean_long_entries, "yellow"), (clean_short_entries, "pink")], #list of entries tuples with color
[(clean_long_exits, "yellow"), (clean_short_exits, "pink")]), #list of exits tuples with color
)
# # Create the chart with the panel
ch = chart([pane1], title="Chart with EntryShort/ExitShort (yellow) and EntryLong/ExitLong markers (pink)", session=None, size="s")
```
<img width="800" alt="image" src="https://github.com/drew2323/lightweight-charts-python/assets/28433232/0acde2bf-600e-4f45-8db0-5077822d6993">
<div align="center">
# lightweight-charts-python
# lightweight-charts-python - forked from
[![PyPi Release](https://img.shields.io/pypi/v/lightweight-charts?color=32a852&label=PyPi)](https://pypi.org/project/lightweight-charts/)
[![Made with Python](https://img.shields.io/badge/Python-3.8+-c7a002?logo=python&logoColor=white)](https://python.org "Go to Python homepage")

View File

Before

Width:  |  Height:  |  Size: 555 KiB

After

Width:  |  Height:  |  Size: 555 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 MiB

After

Width:  |  Height:  |  Size: 5.0 MiB

View File

Before

Width:  |  Height:  |  Size: 349 KiB

After

Width:  |  Height:  |  Size: 349 KiB

View File

Before

Width:  |  Height:  |  Size: 612 KiB

After

Width:  |  Height:  |  Size: 612 KiB

View File

Before

Width:  |  Height:  |  Size: 510 KiB

After

Width:  |  Height:  |  Size: 510 KiB

View File

Before

Width:  |  Height:  |  Size: 475 KiB

After

Width:  |  Height:  |  Size: 475 KiB

BIN
image-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
image-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
image-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
image-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
image-5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
image-6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
image-7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -2,4 +2,4 @@ from .abstract import AbstractChart, Window
from .chart import Chart
from .widgets import JupyterChart
from .polygon import PolygonChart
from .helpers import chart, Panel, PlotAccessor
from .helpers import chart, Panel, PlotSRAccessor, PlotDFAccessor

View File

@ -202,7 +202,7 @@ class SeriesCommon(Pane):
self._set_interval(df)
if not pd.api.types.is_datetime64_any_dtype(df['time']):
df['time'] = pd.to_datetime(df['time'])
df['time'] = df['time'].astype('int64') / 10 ** 9 #removed integer divison // 10 ** 9 to keep subseconds precision
df['time'] = (df['time'].astype('int64') / 10 ** 9).astype(float)
# if 'updated' in df.columns:
# df['updated'] = pd.to_datetime(df['updated']).astype('int64') / 10**9
@ -213,7 +213,7 @@ class SeriesCommon(Pane):
if col == 'time' or not pd.api.types.is_datetime64_any_dtype(df[col]):
continue
# Convert datetime to timestamp with nanosecond precision after the decimal
df[col] = pd.to_datetime(df[col]).astype('int64') / 10**9
df[col] = (pd.to_datetime(df[col]).astype('int64') / 10**9).astype(float)
return df
@ -245,9 +245,12 @@ class SeriesCommon(Pane):
if format_cols:
df = self._df_datetime_format(df, exclude_lowercase=self.name)
if self.name:
if self.name not in df:
if self.name and len(df.columns) == 1: #if only one col rename it
df.columns = ['value']
elif self.name not in df:
raise NameError(f'No column named "{self.name}".')
df = df.rename(columns={self.name: 'value'})
else:
df = df.rename(columns={self.name: 'value'})
self.data = df.copy()
self._last_bar = df.iloc[-1]
self.run_script(f'{self.id}.series.setData({js_data(df)}); ')
@ -646,7 +649,7 @@ class Candlestick(SeriesCommon):
super().__init__(chart)
self._volume_up_color = 'rgba(83,141,131,0.8)'
self._volume_down_color = 'rgba(200,127,130,0.8)'
self.num_decimals = 2
self.candle_data = pd.DataFrame()
# self.run_script(f'{self.id}.makeCandlestickSeries()')
@ -750,11 +753,25 @@ class Candlestick(SeriesCommon):
text_color: Optional[str] = None,
entire_text_only: bool = False,
visible: bool = True,
ticks_visible: bool = False,
ticks_visible: bool = True,
minimum_width: int = 0
):
# def precision(self, precision: int):
# """
# Sets the precision and minMove.\n
# :param precision: The number of decimal places.
# """
# min_move = 1 / (10**precision)
# self.run_script(f'''
# {self.id}.series.applyOptions({{
# priceFormat: {{precision: {precision}, minMove: {min_move}}}
# }})''')
# self.num_decimals = precision
self.run_script(f'''
{self.id}.series.priceScale().applyOptions({{
priceFormat: {{ precision: 3, minMove: 0.005 }},
autoScale: {jbool(auto_scale)},
mode: {as_enum(mode, PRICE_SCALE_MODE)},
invertScale: {jbool(invert_scale)},
@ -985,7 +1002,7 @@ class AbstractChart(Candlestick, Pane):
def legend(self, visible: bool = False, ohlc: bool = True, percent: bool = True, lines: bool = True,
color: str = 'rgb(191, 195, 203)', font_size: int = 11, font_family: str = 'Monaco',
text: str = '', color_based_on_candle: bool = False):
text: str = '', color_based_on_candle: bool = True):
"""
Configures the legend of the chart.
"""

View File

@ -1,18 +1,89 @@
from ast import parse
from .widgets import JupyterChart
from .util import (
is_vbt_indicator, get_next_color
)
import pandas as pd
#default settings for each pricescale
ohlcv_cols = ['close', 'volume', 'open', 'high', 'low']
right_cols = ['vwap']
left_cols = ['rsi', 'cci', 'macd', 'macdsignal', "chopiness", "chopiness_ma"]
middle1_cols = ["mom"]
middle2_cols = ["updated", "integer"]
histogram_cols = ['buyvolume', 'sellvolume', 'trades', 'macdhist']
def append_scales(df, right, histogram, left, middle1, middle2, name = ""):
if isinstance(df, pd.DataFrame):
for col in df.columns:
match col:
case c if c.lower() in ohlcv_cols:
continue
case c if c.lower() in right_cols:
right.append((df[c],name+c,))
case c if c.lower() in histogram_cols:
histogram.append((df[c],name+c,))
case c if c.lower() in left_cols:
left.append((df[c],name+c,))
case c if c.lower() in middle1_cols:
middle1.append((df[c],name+c,))
case c if c.lower() in middle2_cols:
middle2.append((df[c],name+c,))
case _:
right.append((df[c],name+c,))
else: #it is series (as df multiindex can be just envelope for series)
right.append((df,str(df.name),))
def append_or_extend(target_list, value):
if isinstance(value, list):
target_list.extend(value) # Extend if it's a list
else:
target_list.append(value) # Append if it's a single value
def extend_kwargs(ohlcv, right, left, middle1, middle2, histogram, auto_scale, kwargs):
"""
Mutate lists based on kwargs for accessor.
Used when user added additional series to kwargs when using accessor.
"""
if 'ohlcv' in kwargs:
ohlcv = kwargs['ohlcv'] #ohlcv is only a tuple
if 'left' in kwargs:
append_or_extend(left, kwargs['left'])
if 'right' in kwargs:
append_or_extend(right, kwargs['right'])
if 'histogram' in kwargs:
append_or_extend(histogram, kwargs['histogram'])
if 'middle1' in kwargs:
append_or_extend(middle1, kwargs['middle1'])
if 'middle2' in kwargs:
append_or_extend(middle2, kwargs['middle2'])
if 'auto_scale' in kwargs:
append_or_extend(auto_scale, kwargs['auto_scale'])
return ohlcv #as tuple is immutable
# Register the custom accessor
@pd.api.extensions.register_series_accessor("lw")
class PlotAccessor:
class PlotSRAccessor:
"""
Custom plot accessor for pandas series.
Custom plot accessor for pandas series. Quickly displays series values as line on the single pane.
Also additional priceseries can be added on top of them. They can be added
for each scale in the correct format - either as tuple(OHLCV) or as list of tuple (others)
# input parameter / expected format:
# ohlcv=(), #(series, entries, exits, other_markers)
# histogram=[], # [(series, name, "rgba(53, 94, 59, 0.6)", opacity)]
# right=[],
# left=[], #[(series, name, entries, exits, other_markers)]
# middle1=[],
# middle2=[],
Usage: s
series.lw.plot()
series.lw.plot(size="m")
series.lw.plot() #plot series as line
series.lw.plot(size="m") #on medium panesize
series.lw.plot(histogram=(trade_series, "trades")) #plot histogram with trades on top of that
"""
def __init__(self, pandas_obj):
self._obj = pandas_obj
@ -20,11 +91,123 @@ class PlotAccessor:
def plot(self, **kwargs):
if "size" not in kwargs:
kwargs["size"] = "xs"
name = kwargs["name"] if "name" in kwargs else "line"
ohlcv = ()
right = []
left = []
middle1 = []
middle2 = []
histogram = []
auto_scale = []
#if there are additional series in kwargs add them too
#ohlcv is returned as it is tuple thus immutable
ohlcv = extend_kwargs(ohlcv, right, left, middle1, middle2, histogram, auto_scale, kwargs)
right.append((self._obj,name))
pane1 = Panel(
right=[(self._obj, "line")],
)
auto_scale=auto_scale,
ohlcv=ohlcv,
histogram=histogram,
right=right,
left=left,
middle1=middle1,
middle2=middle2
)
ch = chart([pane1], **kwargs)
@pd.api.extensions.register_dataframe_accessor("lw")
class PlotDFAccessor:
"""
Custom plot accessor for dataframe. Quickly displays all columns on the single pane.
Series type is automatically extracted for each column based on following setting:
scale / columns
ohlcv = ['close', 'volume', 'open', 'high', 'low']
right = ['vwap']
left = ['rsi']
middle1 = []
middle2 = []
histogram = ['buyvolume', 'sellvolume', 'trades']
Also additional priceseries can be added on top of them as parameters. They can be added
for each scale in the correct format - either as tuple(OHLCV) or as list of tuple (others)
# input parameter / expected format:
# ohlcv=(), #(series, entries, exits, other_markers)
# histogram=[], # [(series, name, "rgba(53, 94, 59, 0.6)", opacity)]
# right=[],
# left=[], #[(series, name, entries, exits, other_markers)]
# middle1=[],
# middle2=[],
Usage:
ohlcv_df.lw.plot()
ohlcv_df.lw.plot(size="m")
ohlcv_df.lw.plot(right=(rsi_series, "rsi"))
ohlcv_df.lw.plot(right=[(rsi_series, "rsi"), (angle_series, "angle")])
basic_data.data[SYMBOL].lw.plot(histogram=(basic_data.data[SYMBOL].close, "close"), size="m")
"""
def __init__(self, pandas_obj):
self._obj = pandas_obj
def plot(self, **kwargs):
if "size" not in kwargs:
kwargs["size"] = "xs"
ohlcv = ()
right = []
left = []
middle1 = []
middle2 = []
histogram = []
auto_scale = []
if isinstance(self._obj.columns, pd.MultiIndex):
for col_tuple in self._obj.columns:
# Access the data for each column tuple dynamically
df = self._obj.loc[:, col_tuple]
name = str(col_tuple)+" "
append_scales(df, right, histogram, left, middle1, middle2, name)
first_column_df = self._obj.loc[:, self._obj.columns[0]]
ohlcv = (first_column_df[ohlcv_cols],) if isinstance(first_column_df, pd.DataFrame) and first_column_df.columns in ohlcv else () #in case of multiindex only the first ohlcv is display only one ohlcv is allowed on the pane
else:
append_scales(self._obj, right, histogram, left, middle1, middle2)
#add ohlcv if all columns ohlcv_cols
#column mapping enables either both lowercase and first upper
column_mapping = {key: next((col for col in self._obj.columns if col.lower() == key), None) for key in ohlcv_cols}
mapped_columns = [column_mapping[key] for key in ohlcv_cols if column_mapping[key] is not None]
ohlcv = (self._obj[mapped_columns],) if isinstance(self._obj, pd.DataFrame) and all(col in self._obj.columns.str.lower() for col in ohlcv_cols) else ()
#if there are additional series in kwargs add them too
ohlcv = extend_kwargs(ohlcv, right, left, middle1, middle2, histogram, auto_scale, kwargs)
pane1 = Panel(
auto_scale=auto_scale,
ohlcv=ohlcv,
histogram=histogram,
right=right,
left=left,
middle1=middle1,
middle2=middle2
)
ch = chart([pane1], **kwargs)
# pane1 = Panel(
# ohlcv=(), #(series, entries, exits, other_markers)
# histogram=[], # [(series, name, "rgba(53, 94, 59, 0.6)", opacity)]
# right=[],
# left=[], #[(series, name, entries, exits, other_markers)]
# middle1=[],
# middle2=[],
# )
class Panel:
"""
A class to represent a panel in a chart.
@ -44,6 +227,7 @@ class Panel:
* left : list of tuples, optional
* middle1 : list of tuples, optional
* middle2 : list of tuples, optional
* auto_scale: list of objects, optional - external objects (vbt indicators) that can be automatically parsed to given scaleID
* xloc : str or slice, optional. Vectorbt indexing. Default is None.
* precision: int, optional. The number of digits after the decimal point. Apply to all lines on this pane. Default is None.
@ -62,6 +246,18 @@ class Panel:
ch = chart([pane1])
# or simply:
Panel(
auto_scale=[cdlbreakaway],
ohlcv=(t1data.ohlcv.data["BAC"],entries),
histogram=[],
right=[],
left=[],
middle1=[],
middle2=[]
).chart(size="s")
# Synced example
pane1 = Panel(
ohlcv=(t1data.data["BAC"],), #(series, entries, exits, other_markers)
@ -75,6 +271,7 @@ class Panel:
)
pane2 = Panel(
auto_scale=[macd_vbt_ind],
ohlcv=(t1data.data["BAC"],),
right=[],
left=[(sma, "sma_below", short_signals, short_exits)],
@ -106,7 +303,8 @@ class Panel:
ch = chart([pane1], title="Chart with EntryShort/ExitShort (yellow) and EntryLong/ExitLong markers (pink)", sync=True, session=None, size="s")
```
"""
def __init__(self, ohlcv=None, right=None, left=None, middle1=None, middle2=None, histogram=None, title=None, xloc=None, precision=None):
def __init__(self, auto_scale=[],ohlcv=None, right=None, left=None, middle1=None, middle2=None, histogram=None, title=None, xloc=None, precision=None):
self.auto_scale = auto_scale
self.ohlcv = ohlcv if ohlcv is not None else ()
self.right = right if right is not None else []
self.left = left if left is not None else []
@ -117,8 +315,10 @@ class Panel:
self.xloc = xloc
self.precision = precision
def chart(panes: list[Panel], sync=False, title='', size="m", xloc=None, session: str="9:30:00", precision=None):
def chart(self, **kwargs):
chart([self], **kwargs)
def chart(panes: list[Panel], sync=False, title='', size="s", xloc=None, session = slice("09:30:00","9:30:05"), precision=None, params_detail=False, **kwargs):
"""
Function to fast render a chart with multiple panes. This function manipulates graphical
output or interfaces with an external framework to display charts with synchronized
@ -144,6 +344,8 @@ def chart(panes: list[Panel], sync=False, title='', size="m", xloc=None, session
* precision (int): The number of digits after the decimal point. Defaults to None. Applies to lines on all panes, if not overriden by pane-specific precision.
* params_detail (bool): If True displays in the legend full names of multiindex columns.
* xloc (str): xloc advanced filtering of vbt.xloc accessor. Defaults to None. Applies to all panes.
Might be overriden by pane-specific xloc.
@ -230,6 +432,79 @@ def chart(panes: list[Panel], sync=False, title='', size="m", xloc=None, session
active_chart.markers_set(markers=xloc_me(markers, xloc), type=type, color=color if color is not None else None)
def add_to_scale(series, right, histogram, left, middle1, middle2, column,name = None):
"""
Assigns a series to a scaleId based on its name and pre-defined col names.
Args:
-----
series (pd.Series): The series to be added to a scaleId
right (list): The right scale to add to
histogram (list): The histogram scale to add to
left (list): The left scale to add to
middle1 (list): The middle1 scale to add to
middle2 (list): The middle2 scale to add to
name (str): The name of the series
Returns:
-------
None
Notes:
-----
The function checks if the series name is in the pre-defined column names
(e.g. ohlcv_cols, right_cols, histogram_cols, etc.) and assigns the series to
the corresponding scaleId. If the name is not found in any of the pre-defined
column names, the series is added to the right scale by default.
"""
if name is None:
name = column
if column.lower() in ohlcv_cols:
return
elif column.lower() in right_cols:
right.append((series, name,))
elif column.lower() in histogram_cols:
histogram.append((series, name))
elif column.lower() in left_cols:
left.append((series, name))
elif column.lower() in middle1_cols:
middle1.append((series, name))
elif column.lower() in middle2_cols:
middle2.append((series, name))
else:
right.append((series, name,))
# automatic scale assignment
if len(pane.auto_scale) > 0:
for obj in pane.auto_scale:
if is_vbt_indicator(obj): #for vbt indicators
for output in obj.output_names:
output_series = getattr(obj, output)
output_name = obj.short_name + ':' + output
output = obj.short_name if output == "real" else output
#if output_series is multiindex - add each combination to respective scaleId
if isinstance(output_series, pd.DataFrame) and isinstance(output_series.columns, pd.MultiIndex):
for col_tuple in output_series.columns:
name=output_name + " " + str(col_tuple)
series_copy = output_series.loc[:, col_tuple].copy(deep=True)
add_to_scale(series_copy, pane.right, pane.histogram, pane.left, pane.middle1, pane.middle2, output, name)
elif isinstance(output_series, pd.DataFrame): #in case of multicolumns
for col in output_series.columns:
name=output_name + " " + col
series_copy = output_series.loc[:, col].copy(deep=True)
add_to_scale(series_copy, pane.right, pane.histogram, pane.left, pane.middle1, pane.middle2, output, name)
# elif isinstance(output_series, pd.DataFrame): #it df with multiple columns (probably symbols)
# for col in output_series.columns:
# name=output_name + " " + col
# series_copy = output_series[col].squeeze()
# add_to_scale(series_copy, pane.right, pane.histogram, pane.left, pane.middle1, pane.middle2, output, name)
else: #add output to respective scale
series_copy = output_series.copy(deep=True)
add_to_scale(series_copy, pane.right, pane.histogram, pane.left, pane.middle1, pane.middle2, output, output_name)
# zde jsem skoncil
#vbt ind
if pane.ohlcv != ():
series, entries, exits, markers = (pane.ohlcv + (None,) * 4)[:4]
if series is None:
@ -252,8 +527,19 @@ def chart(panes: list[Panel], sync=False, title='', size="m", xloc=None, session
kwargs['color'] = color
if opacity is not None:
kwargs['opacity'] = opacity
tmp = active_chart.create_histogram(**kwargs) #green transparent "rgba(53, 94, 59, 0.6)"
tmp.set(xloc_me(series, xloc))
if isinstance(series, pd.DataFrame) and isinstance(series.columns, pd.MultiIndex): #multiindex handling
for col_tuple in series.columns:
kwargs = {'name': name + str(col_tuple)}
tmp = active_chart.create_histogram(**kwargs) #green transparent "rgba(53, 94, 59, 0.6)"
tmp.set(xloc_me(series.loc[:, col_tuple], xloc))
elif isinstance(series, pd.DataFrame): #it df with multiple columns (probably symbols)
for col in series.columns:
kwargs = {'name': name + str(col)}
tmp = active_chart.create_histogram(**kwargs) #green transparent "rgba(53, 94, 59, 0.6)"
tmp.set(xloc_me(series[col], xloc))
else:
tmp = active_chart.create_histogram(**kwargs) #green transparent "rgba(53, 94, 59, 0.6)"
tmp.set(xloc_me(series, xloc))
if pane.title is not None:
active_chart.topbar.textbox("title",pane.title)
@ -261,7 +547,7 @@ def chart(panes: list[Panel], sync=False, title='', size="m", xloc=None, session
#iterate over keys - they are all priceScaleId except of these
for att_name, att_value_tuple in vars(pane).items():
if att_name in ["ohlcv","histogram","title","xloc","precision"]:
if att_name in ["ohlcv","histogram","title","xloc","precision", "auto_scale"]:
continue
for tup in att_value_tuple:
series, name, entries, exits, markers = (tup + (None, None, None, None, None))[:5]
@ -273,12 +559,71 @@ def chart(panes: list[Panel], sync=False, title='', size="m", xloc=None, session
series = series.xloc[xloc] if xloc is not None else series
for output in series.output_names:
output_series = getattr(series, output)
output = name + ':' + output if name is not None else output
tmp = active_chart.create_line(name=output, priceScaleId=att_name)#, color="blue")
tmp.set(output_series)
output = name + ':' + output if name is not None else series.short_name + ":" + output
#if output_series is multiindex - create aline for each combination
if isinstance(output_series, pd.DataFrame) and isinstance(output_series.columns, pd.MultiIndex):
for col_tuple in output_series.columns:
tmp = active_chart.create_line(name=output + " " + str(col_tuple), priceScaleId=att_name)#, color="blue")
tmp.set(output_series.loc[:, col_tuple])
elif isinstance(output_series, pd.DataFrame): #it df with multiple columns (probably symbols)
for col in output_series.columns:
tmp = active_chart.create_line(name=output + " " + str(col), priceScaleId=att_name)#, color="blue")
tmp.set(output_series[col])
else:
tmp = active_chart.create_line(name=output, priceScaleId=att_name)#, color="blue")
tmp.set(output_series)
#if multiindex then unpack them all with tuple as names
elif isinstance(series, pd.DataFrame) and isinstance(series.columns, pd.MultiIndex):
for col_tuple in series.columns:
#if required show all multiindex names
if params_detail:
# Access MultiIndex level names
index_names = series.columns.names
# Build the name string by combining level names and their corresponding values
col_name_str = " ".join([f"{index_names[i]}: {col_val}" for i, col_val in enumerate(col_tuple)])
# Use this name in the chart, adding the provided name if it exists
final_name = col_name_str if name is None else f"{name} {col_name_str}"
else:
final_name = str(col_tuple) if name is None else name+" "+str(col_tuple)
tmp = active_chart.create_line(name=final_name, priceScaleId=att_name)#, color="blue")
tmp.set(xloc_me(series.loc[:, col_tuple], xloc))
elif isinstance(series, pd.DataFrame): #it df with multiple columns (probably symbols)
#recursive df handling - but make sure it
def traverse_dataframe(series, att_name, xloc, active_chart, name=""):
nonlocal tmp
# Check if the input is a DataFrame
if isinstance(series, pd.DataFrame):
for col in series.columns:
col_name = name + " " + col if name else col
# Recursively call the function for each column
traverse_dataframe(series[col], att_name, xloc, active_chart, col_name)
elif isinstance(series, pd.Series):
# Once we hit the series level, create the result_name and call the active_chart method
result_name = name.strip() # Remove any leading/trailing spaces from column name
result_series = series.squeeze() # Extract the series data
# Now call the `active_chart.create_line()` as per your requirement
tmp = active_chart.create_line(name=result_name, priceScaleId=att_name)
tmp.set(xloc_me(result_series, xloc)) # Call the xloc_me function for setting xloc
else:
raise ValueError(f"Unexpected type {type(series)} encountered")
if name is None:
name = "no_name" if not hasattr(series, 'name') or series.name is None else str(series.name)
traverse_dataframe(series, att_name, xloc, active_chart, name)
# for col in series.columns:
# name=name + " " + col
# series_copy = output_series[col].squeeze()
# tmp = active_chart.create_line(name=name, priceScaleId=att_name)#, color="blue")
# tmp.set(xloc_me(series_copy, xloc))
else:
if name is None:
name = "no_name"
name = "no_name" if not hasattr(series, 'name') or series.name is None else str(series.name)
tmp = active_chart.create_line(name=name, priceScaleId=att_name)#, color="blue")
tmp.set(xloc_me(series, xloc))
@ -296,8 +641,22 @@ def chart(panes: list[Panel], sync=False, title='', size="m", xloc=None, session
active_chart.legend(True)
active_chart.fit()
if session is not None and session:
last_used_series = output_series if is_vbt_indicator(series) else series #pokud byl posledni series vbt, pak pouzijeme jeho outputy
active_chart.vertical_span(start_time=xloc_me(last_used_series, xloc).vbt.xloc[session].obj.index.to_list(), color="rgba(252, 255, 187, 0.42)")
try:
last_used_series = output_series.loc[:, col_tuple] if isinstance(output_series, pd.DataFrame) and isinstance(output_series.columns, pd.MultiIndex) else output_series if is_vbt_indicator(series) else series #pokud byl posledni series vbt, pak pouzijeme jeho outputy
last_used_series = last_used_series.iloc[:,0] if isinstance(last_used_series, pd.DataFrame) else last_used_series #if df then use just first column
t1 = xloc_me(last_used_series, xloc)
t1 = t1.vbt.xloc[session]
target_data = t1.obj
#we dont know the exact time of market start +- 3 seconds thus we find mark first row after 9:30
# Resample the data to daily frequency and get the first entry of each day
first_row_indexes = target_data.resample('D').apply(lambda x: x.index[0] if not x.empty else None).dropna()
# Convert the indexes to a list
session_starts = first_row_indexes.to_list()
active_chart.vertical_span(start_time=session_starts, color="rgba(252, 255, 187, 0.42)")
except Exception as e:
print("Error fetching main session")
if not main_title_set:
chartX.topbar.textbox("title",title)

File diff suppressed because one or more lines are too long

View File

@ -113,7 +113,7 @@ def is_vbt_indicator(variable):
# Get the module path of the variable's type
module_path = variable.__class__.__module__
# Check if it starts with 'vectorbtpro.indicators'
return module_path.startswith('vectorbtpro.indicators')
return module_path.startswith('vectorbtpro.indicators') or module_path.startswith('indicators.')
class Pane:
def __init__(self, window):

View File

@ -5,7 +5,7 @@ with open('README.md', 'r', encoding='utf-8') as f:
setup(
name='lightweight_charts',
version='2.1.4',
version='2.2.29',
packages=find_packages(),
python_requires='>=3.8',
install_requires=[

View File

@ -40,7 +40,7 @@ export class Handler {
public chart: IChartApi;
public scale: Scale;
public precision: number = 2;
public precision: number = 3;
public series: ISeriesApi<SeriesType>;
public volumeSeries: ISeriesApi<SeriesType>;

View File

@ -1,7 +1,6 @@
import { ISeriesApi, LineData, Logical, MouseEventParams, PriceFormatBuiltIn, SeriesType } from "lightweight-charts";
import { Handler } from "./handler";
interface LineElement {
name: string;
div: HTMLDivElement;
@ -14,6 +13,11 @@ interface LineElement {
export class Legend {
private handler: Handler;
public div: HTMLDivElement;
public contentWrapper: HTMLDivElement;
private collapseButton: HTMLDivElement;
private toggleAllButton: HTMLDivElement;
private isCollapsed: boolean = false;
private allVisible: boolean = true;
private ohlcEnabled: boolean = false;
private percentEnabled: boolean = false;
@ -24,74 +28,214 @@ export class Legend {
private candle: HTMLDivElement;
public _lines: LineElement[] = [];
constructor(handler: Handler) {
this.legendHandler = this.legendHandler.bind(this)
this.handler = handler;
this.ohlcEnabled = false;
this.percentEnabled = false
this.linesEnabled = false
this.colorBasedOnCandle = false
this.div = document.createElement('div');
this.div.classList.add('legend');
this.div.style.maxWidth = `${(handler.scale.width * 100) - 8}vw`
this.div.style.maxWidth = '300px';
this.div.style.minWidth = '200px';
this.div.style.maxHeight = '300px';
this.div.style.overflowY = 'auto';
this.div.style.overflowX = 'hidden';
this.div.style.position = 'absolute';
this.div.style.backgroundColor = 'rgba(19, 23, 34, 0)';
this.div.style.color = '#D1D4DC';
this.div.style.padding = '1px';
this.div.style.borderRadius = '4px';
//this.div.style.border = '1px solid rgba(42, 46, 57, 0.85)';
this.div.style.boxShadow = '0 2px 5px rgba(0,0,0,0.3)';
this.div.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
this.div.style.fontSize = '12px';
this.div.style.zIndex = '5';
this.div.style.display = 'none';
this.div.style.pointerEvents = 'all';
this.text = document.createElement('span')
this.text.style.lineHeight = '1.8'
this.candle = document.createElement('div')
const buttonsContainer = document.createElement('div');
buttonsContainer.style.position = 'absolute';
buttonsContainer.style.right = '8px';
buttonsContainer.style.top = '8px';
buttonsContainer.style.display = 'flex';
buttonsContainer.style.gap = '2px';
buttonsContainer.style.zIndex = '6';
buttonsContainer.style.pointerEvents = 'all';
this.div.appendChild(this.text)
this.div.appendChild(this.candle)
handler.div.appendChild(this.div)
this.collapseButton = document.createElement('div');
this.collapseButton.style.cursor = 'pointer';
this.collapseButton.style.width = '20px';
this.collapseButton.style.height = '20px';
this.collapseButton.style.display = 'flex';
this.collapseButton.style.alignItems = 'center';
this.collapseButton.style.justifyContent = 'center';
this.collapseButton.style.color = '#D1D4DC';
this.collapseButton.style.fontSize = '16px';
this.collapseButton.style.userSelect = 'none';
this.collapseButton.style.pointerEvents = 'all';
this.collapseButton.innerHTML = '';
this.collapseButton.addEventListener('click', (e) => {
e.stopPropagation();
this.toggleCollapse();
});
// this.makeSeriesRows(handler);
this.toggleAllButton = document.createElement('div');
this.toggleAllButton.style.cursor = 'pointer';
this.toggleAllButton.style.width = '20px';
this.toggleAllButton.style.height = '20px';
this.toggleAllButton.style.display = 'flex';
this.toggleAllButton.style.alignItems = 'center';
this.toggleAllButton.style.justifyContent = 'center';
this.toggleAllButton.style.color = '#D1D4DC';
this.toggleAllButton.style.fontSize = '14px';
this.toggleAllButton.style.userSelect = 'none';
this.toggleAllButton.style.pointerEvents = 'all';
this.toggleAllButton.innerHTML = '👁️';
this.toggleAllButton.title = 'Toggle all';
this.toggleAllButton.addEventListener('click', (e) => {
e.stopPropagation();
this.toggleAll();
});
buttonsContainer.appendChild(this.toggleAllButton);
buttonsContainer.appendChild(this.collapseButton);
const style = document.createElement('style');
style.textContent = `
.legend::-webkit-scrollbar {
width: 6px;
}
.legend::-webkit-scrollbar-track {
background: #2A2E39;
}
.legend::-webkit-scrollbar-thumb {
background: #434651;
border-radius: 3px;
}
.legend::-webkit-scrollbar-thumb:hover {
background: #545861;
}
.legend-toggle-switch {
cursor: pointer;
margin-left: 8px;
}
`;
document.head.appendChild(style);
this.contentWrapper = document.createElement('div');
this.contentWrapper.style.minHeight = '100%';
this.contentWrapper.style.width = '100%';
this.contentWrapper.style.display = 'flex';
this.contentWrapper.style.flexDirection = 'column';
this.contentWrapper.style.gap = '1px';
this.contentWrapper.style.marginTop = '20px';
this.contentWrapper.style.pointerEvents = 'all';
this.text = document.createElement('span');
this.text.style.lineHeight = '1';
this.text.style.display = 'block';
this.text.style.color = '#D1D4DC';
this.candle = document.createElement('div');
this.candle.style.color = '#D1D4DC';
this.candle.style.width = '100%';
this.contentWrapper.appendChild(this.text);
this.contentWrapper.appendChild(this.candle);
this.div.appendChild(buttonsContainer);
this.div.appendChild(this.contentWrapper);
handler.div.appendChild(this.div);
handler.chart.subscribeCrosshairMove(this.legendHandler)
}
private toggleCollapse() {
this.isCollapsed = !this.isCollapsed;
if (this.isCollapsed) {
this.contentWrapper.style.display = 'none';
this.div.style.maxHeight = '40px'; // Changed from 'auto' to fixed height for buttons
this.div.style.height = '40px'; // Added fixed height
this.collapseButton.innerHTML = '+';
} else {
this.contentWrapper.style.display = 'flex';
this.div.style.maxHeight = '300px';
this.div.style.height = 'auto'; // Reset height to auto
this.collapseButton.innerHTML = '';
}
}
private toggleAll() {
this.allVisible = !this.allVisible;
this._lines.forEach(line => {
line.series.applyOptions({
visible: this.allVisible
});
const group = line.toggle.querySelector('g');
if (group) {
if (this.allVisible) {
group.innerHTML = this.openEyeSvg(line.solid);
} else {
group.innerHTML = this.closedEyeSvg(line.solid);
}
}
});
this.toggleAllButton.style.opacity = this.allVisible ? '1' : '0.5';
}
private openEyeSvg(strokeColor: string): string {
return `
<path style="fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke:${strokeColor};stroke-opacity:1;stroke-miterlimit:4;" d="M 21.998437 12 C 21.998437 12 18.998437 18 12 18 C 5.001562 18 2.001562 12 2.001562 12 C 2.001562 12 5.001562 6 12 6 C 18.998437 6 21.998437 12 21.998437 12 Z M 21.998437 12 " transform="matrix(0.833333,0,0,0.833333,0,0)"/>
<path style="fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke:${strokeColor};stroke-opacity:1;stroke-miterlimit:4;" d="M 15 12 C 15 13.654687 13.654687 15 12 15 C 10.345312 15 9 13.654687 9 12 C 9 10.345312 10.345312 9 12 9 C 13.654687 9 15 10.345312 15 12 Z M 15 12 " transform="matrix(0.833333,0,0,0.833333,0,0)"/>
`;
}
private closedEyeSvg(strokeColor: string): string {
return `
<path style="fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke:${strokeColor};stroke-opacity:1;stroke-miterlimit:4;" d="M 20.001562 9 C 20.001562 9 19.678125 9.665625 18.998437 10.514062 M 12 14.001562 C 10.392187 14.001562 9.046875 13.589062 7.95 12.998437 M 12 14.001562 C 13.607812 14.001562 14.953125 13.589062 16.05 12.998437 M 12 14.001562 L 12 17.498437 M 3.998437 9 C 3.998437 9 4.354687 9.735937 5.104687 10.645312 M 7.95 12.998437 L 5.001562 15.998437 M 7.95 12.998437 C 6.689062 12.328125 5.751562 11.423437 5.104687 10.645312 M 16.05 12.998437 L 18.501562 15.998437 M 16.05 12.998437 C 17.38125 12.290625 18.351562 11.320312 18.998437 10.514062 M 5.104687 10.645312 L 2.001562 12 M 18.998437 10.514062 L 21.998437 12 " transform="matrix(0.833333,0,0,0.833333,0,0)"/>
`;
}
toJSON() {
// Exclude the chart attribute from serialization
const {_lines, handler, ...serialized} = this;
return serialized;
}
// makeSeriesRows(handler: Handler) {
// if (this.linesEnabled) handler._seriesList.forEach(s => this.makeSeriesRow(s))
// }
makeSeriesRow(name: string, series: ISeriesApi<SeriesType>) {
const strokeColor = series.options().color;
let openEye = `
<path style="fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke:${strokeColor};stroke-opacity:1;stroke-miterlimit:4;" d="M 21.998437 12 C 21.998437 12 18.998437 18 12 18 C 5.001562 18 2.001562 12 2.001562 12 C 2.001562 12 5.001562 6 12 6 C 18.998437 6 21.998437 12 21.998437 12 Z M 21.998437 12 " transform="matrix(0.833333,0,0,0.833333,0,0)"/>
<path style="fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke:${strokeColor};stroke-opacity:1;stroke-miterlimit:4;" d="M 15 12 C 15 13.654687 13.654687 15 12 15 C 10.345312 15 9 13.654687 9 12 C 9 10.345312 10.345312 9 12 9 C 13.654687 9 15 10.345312 15 12 Z M 15 12 " transform="matrix(0.833333,0,0,0.833333,0,0)"/>\`
`
let closedEye = `
<path style="fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke:${strokeColor};stroke-opacity:1;stroke-miterlimit:4;" d="M 20.001562 9 C 20.001562 9 19.678125 9.665625 18.998437 10.514062 M 12 14.001562 C 10.392187 14.001562 9.046875 13.589062 7.95 12.998437 M 12 14.001562 C 13.607812 14.001562 14.953125 13.589062 16.05 12.998437 M 12 14.001562 L 12 17.498437 M 3.998437 9 C 3.998437 9 4.354687 9.735937 5.104687 10.645312 M 7.95 12.998437 L 5.001562 15.998437 M 7.95 12.998437 C 6.689062 12.328125 5.751562 11.423437 5.104687 10.645312 M 16.05 12.998437 L 18.501562 15.998437 M 16.05 12.998437 C 17.38125 12.290625 18.351562 11.320312 18.998437 10.514062 M 5.104687 10.645312 L 2.001562 12 M 18.998437 10.514062 L 21.998437 12 " transform="matrix(0.833333,0,0,0.833333,0,0)"/>
`
let row = document.createElement('div')
row.style.display = 'flex'
row.style.alignItems = 'center'
row.style.padding = '0px 0'
row.style.color = '#D1D4DC'
row.style.width = '100%'
row.style.pointerEvents = 'all'
row.style.cursor = 'default'
let div = document.createElement('div')
div.style.flex = '1'
let toggle = document.createElement('div')
toggle.classList.add('legend-toggle-switch');
let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("width", "22");
svg.setAttribute("height", "16");
let group = document.createElementNS("http://www.w3.org/2000/svg", "g");
group.innerHTML = openEye
group.innerHTML = this.openEyeSvg(strokeColor);
let on = true
toggle.addEventListener('click', () => {
if (on) {
on = false
group.innerHTML = closedEye
group.innerHTML = this.closedEyeSvg(strokeColor);
series.applyOptions({
visible: false
})
@ -100,7 +244,7 @@ export class Legend {
series.applyOptions({
visible: true
})
group.innerHTML = openEye
group.innerHTML = this.openEyeSvg(strokeColor);
}
})
@ -108,7 +252,7 @@ export class Legend {
toggle.appendChild(svg);
row.appendChild(div)
row.appendChild(toggle)
this.div.appendChild(row)
this.contentWrapper.appendChild(row)
const color = series.options().color;
this._lines.push({
@ -121,7 +265,9 @@ export class Legend {
});
}
legendItemFormat(num: number, decimal: number) { return num.toFixed(decimal).toString().padStart(8, ' ') }
legendItemFormat(num: number, decimal: number) {
return num.toFixed(decimal).toString().padStart(8, ' ')
}
shorthandFormat(num: number) {
const absNum = Math.abs(num)
@ -132,20 +278,19 @@ export class Legend {
}
return num.toString().padStart(8, ' ');
}
legendHandler(param: MouseEventParams, usingPoint= false) {
if (!this.ohlcEnabled && !this.linesEnabled && !this.percentEnabled) return;
const options: any = this.handler.series.options()
if (!param.time) {
this.candle.style.color = 'transparent'
this.candle.innerHTML = this.candle.innerHTML.replace(options['upColor'], '').replace(options['downColor'], '')
this.candle.innerHTML = this.candle.innerHTML.replace(options['upColor'], '').replace(options['downColor'], '')
return
}
let data: any;
let logical: Logical | null = null;
if (usingPoint) {
const timeScale = this.handler.chart.timeScale();
let coordinate = timeScale.timeToCoordinate(param.time)
@ -157,9 +302,9 @@ export class Legend {
else {
data = param.seriesData.get(this.handler.series);
}
this.candle.style.color = ''
let str = '<span style="line-height: 1.8;">'
let str = '<span style="line-height: 1.0;">'
if (data) {
if (this.ohlcEnabled) {
str += `O ${this.legendItemFormat(data.open, this.handler.precision)} `
@ -167,19 +312,19 @@ export class Legend {
str += `| L ${this.legendItemFormat(data.low, this.handler.precision)} `
str += `| C ${this.legendItemFormat(data.close, this.handler.precision)} `
}
if (this.percentEnabled) {
let percentMove = ((data.close - data.open) / data.open) * 100
let color = percentMove > 0 ? options['upColor'] : options['downColor']
let percentStr = `${percentMove >= 0 ? '+' : ''}${percentMove.toFixed(2)} %`
if (this.colorBasedOnCandle) {
str += `| <span style="color: ${color};">${percentStr}</span>`
} else {
str += '| ' + percentStr
}
}
if (this.handler.volumeSeries) {
let volumeData: any;
if (logical) {
@ -194,14 +339,14 @@ export class Legend {
}
}
this.candle.innerHTML = str + '</span>'
this._lines.forEach((e) => {
if (!this.linesEnabled) {
e.row.style.display = 'none'
return
}
e.row.style.display = 'flex'
let data
if (usingPoint && logical) {
data = e.series.dataByIndex(logical) as LineData
@ -209,15 +354,15 @@ export class Legend {
else {
data = param.seriesData.get(e.series) as LineData
}
if (!data?.value) return;
if (data === undefined || data.value === undefined) return;
let price;
if (e.series.seriesType() == 'Histogram') {
price = this.shorthandFormat(data.value)
} else {
const format = e.series.options().priceFormat as PriceFormatBuiltIn
price = this.legendItemFormat(data.value, format.precision) // couldn't this just be line.options().precision?
price = this.legendItemFormat(data.value, format.precision)
}
e.div.innerHTML = `<span style="color: ${e.solid};">▨ ${e.name} : ${price}</span>`
})
}
}
}