support for markers list

This commit is contained in:
David Brazda
2024-06-27 13:47:12 +02:00
parent b01df74707
commit 3a859c97fc
4 changed files with 123 additions and 84 deletions

View File

@ -14,78 +14,12 @@ from .topbar import TopBar
from .util import ( from .util import (
BulkRunScript, Pane, Events, IDGen, as_enum, jbool, js_json, TIME, NUM, FLOAT, BulkRunScript, Pane, Events, IDGen, as_enum, jbool, js_json, TIME, NUM, FLOAT,
LINE_STYLE, MARKER_POSITION, MARKER_SHAPE, CROSSHAIR_MODE, MARKER_TYPE, LINE_STYLE, MARKER_POSITION, MARKER_SHAPE, CROSSHAIR_MODE, MARKER_TYPE,
PRICE_SCALE_MODE, marker_position, marker_shape, js_data, is_vbt_indicator, apply_opacity PRICE_SCALE_MODE, marker_position, marker_shape, js_data, is_vbt_indicator, apply_opacity, get_next_color
) )
current_dir = os.path.dirname(os.path.abspath(__file__)) current_dir = os.path.dirname(os.path.abspath(__file__))
INDEX = os.path.join(current_dir, 'js', 'index.html') INDEX = os.path.join(current_dir, 'js', 'index.html')
# # Predefined colors that stand out well on dark backgrounds
# COLORS = [
# 'rgba(255, 0, 0, 0.6)', # Red
# 'rgba(0, 255, 0, 0.6)', # Green
# 'rgba(0, 0, 255, 0.6)', # Blue
# 'rgba(255, 255, 0, 0.6)', # Yellow
# 'rgba(255, 165, 0, 0.6)', # Orange
# 'rgba(75, 0, 130, 0.6)', # Indigo
# 'rgba(238, 130, 238, 0.6)', # Violet
# 'rgba(0, 255, 255, 0.6)', # Cyan
# 'rgba(255, 192, 203, 0.6)', # Pink
# 'rgba(0, 128, 128, 0.6)', # Teal
# 'rgba(128, 0, 128, 0.6)', # Purple
# 'rgba(255, 215, 0, 0.6)', # Gold
# 'rgba(173, 255, 47, 0.6)', # Green Yellow
# ]
# def get_next_color():
# return random.choice(COLORS)
# Predefined pool of colors
COLORS = [
"#63AA57", "#8F8AB0", "#E24AEE", "#D06AA6", "#7891BA", "#A39A34", "#8A94A2", "#61BB2F",
"#FD569D", "#1EB6E1", "#379AC9", "#FD6F2E", "#8C9858", "#39A4A3", "#6D97F4", "#1ECB01", "#FA5B16", "#A6891C",
"#48CF10", "#D27B26", "#D56B55", "#FE3AB8", "#E35C51", "#EC4FE6", "#E250A3", "#BA618E", "#1BC074", "#C57784",
"#888BC5", "#4FA452", "#80885C", "#B97272", "#33BF98", "#B7961D", "#A07284", "#02E54E", "#AF7F35", "#F852EF",
"#6D955B", "#E0676E", "#F73DEC", "#CE53FD", "#9773D3", "#649E81", "#D062CE", "#AB73E7", "#A4729C", "#E76A07",
"#E85CCB", "#A16FB1", "#4BB859", "#B25EE2", "#8580CE", "#A275EF", "#AC9245", "#4D988D", "#B672C9", "#4CA96E",
"#C9873E", "#5BB147", "#10C783", "#D7647D", "#CB893A", "#A586BA", "#28C0A2", "#61A755", "#0EB7C5", "#2DADBC",
"#17BB71", "#2BC733", "#2BB890", "#F04EF8", "#699580", "#A88809", "#EB3FF6", "#A75ED3", "#859171", "#BB6285",
"#81A147", "#AD7CD2", "#65B630", "#C9616C", "#BD5EFA", "#7A9F30", "#2AB6AB", "#FC496A", "#687FC7", "#DB40E7",
"#07BCE9", "#509F63", "#EC4FDD", "#A079BE", "#C17297", "#E447C2", "#E95AD9", "#9FA01E", "#7E86CF", "#21E316",
"#1CABF9", "#17C24F", "#9C9254", "#C97994", "#4BA9DA", "#0DD595", "#13BEA8", "#C2855D", "#DF6C13", "#60B370",
"#0FC3F6", "#C1830E", "#3AC917", "#0EBBB0", "#CC50B4", "#B768EC", "#D47F49", "#B47BC5", "#38ADBD", "#05DC53",
"#44CD4E", "#838E65", "#49D70F", "#2DADBE", "#2CB0C9", "#DA703E", "#06B5CA", "#7BAF3E", "#918E79", "#2AA5E5",
"#C37F5E", "#07B8C9", "#4CBA27", "#E752C6", "#7F93B2", "#4798CD", "#45AA4C", "#4DB666", "#7683A7", "#758685",
"#4B9FAD", "#9280FD", "#6682DD", "#42ACBE", "#C1609F", "#D850DB", "#649A62", "#54CC22", "#AD81C1", "#BF7A43",
"#0FCEA5", "#D06DAF", "#87799B", "#4DA94E", "#2FD654", "#07D587", "#21CF0C", "#03CF34", "#42C771", "#D563CD",
"#6D9E9A", "#C76C59", "#68B368", "#11BCE5", "#0DCFB3", "#9266D8", "#BF67F6", "#88A04E", "#73BE17", "#67B437",
"#8586E4", "#9F8749", "#479CA5", "#CC777E", "#4FAF46", "#9D9836", "#918DAF", "#D167B8", "#6F9DA5", "#2BB167",
"#16B8BC", "#B4861F", "#A08487", "#67B357", "#5CAA5C", "#20CA49", "#D18813", "#15D63F", "#C8618F", "#887E92",
"#21C457", "#4EA8CE", "#53BE49", "#5A86D5", "#BD7E4E", "#27B0A1", "#33CF42", "#709083", "#38A8DE", "#4CA762",
"#1EA4FF", "#DE3EE4", "#70A860", "#39A3C8", "#6BBB39", "#F053F4", "#8C7FB5", "#969F21", "#B19841", "#E57148",
"#C25DA7", "#6DA979", "#B27D73", "#7F9786", "#41AC99", "#C58848", "#948F9E", "#6BB620", "#81AB3B", "#09DE44",
"#43A9D2", "#41B0D7", "#20ACAA", "#649FCB", "#CD8345", "#A88669", "#3EA5E7", "#F36A19", "#E06B48", "#8388BD",
"#EC6153", "#639082", "#52CA32", "#878BAA", "#02BCDB", "#828FD9", "#3DC07F", "#29D46A", "#9C7CC1", "#EB7713",
"#F95F6A", "#E25F4C", "#589994", "#D45AB7", "#DE66AB", "#B8715F", "#E850F4", "#FB6420", "#C2832C", "#6383C5",
"#D57A58", "#EF652C", "#02D71A", "#ED664D", "#60A526"
]
# Iterator to keep track of the current color index
color_index = 0
def get_next_color():
global color_index
# Get the next color from the list
color = COLORS[color_index]
# Convert the color from HEX to RGBA format
color_index = (color_index + 1) % len(COLORS)
return hex_to_rgba(color)
def hex_to_rgba(hex_color, alpha=0.5):
hex_color = hex_color.lstrip('#')
r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
return f'rgba({r}, {g}, {b}, {alpha})'
class Window: class Window:
_id_gen = IDGen() _id_gen = IDGen()
handlers = {} handlers = {}
@ -336,7 +270,7 @@ class SeriesCommon(Pane):
col_name: Optional[str] = None, col_name: Optional[str] = None,
position: MARKER_POSITION = 'below', position: MARKER_POSITION = 'below',
shape: MARKER_SHAPE = 'arrow_up', shape: MARKER_SHAPE = 'arrow_up',
color: str = '#2196F3', text: str = ''): color: str = None, text: str = ''):
""" """
Adds multiple markers from pd series or Dataframe Adds multiple markers from pd series or Dataframe
:param markers: A pandas Series or Dataframe with DateTimeIndex and boolean values. :param markers: A pandas Series or Dataframe with DateTimeIndex and boolean values.
@ -356,11 +290,11 @@ class SeriesCommon(Pane):
case "entries": case "entries":
position = "below" position = "below"
shape = "arrow_up" shape = "arrow_up"
color = "blue" color = "blue" if color is None else color
case "exits": case "exits":
position = "above" position = "above"
shape = "arrow_down" shape = "arrow_down"
color = "red" color = "red" if color is None else color
if isinstance(markers, pd.Series): if isinstance(markers, pd.Series):
markers = markers.to_frame(name="markers") markers = markers.to_frame(name="markers")

View File

@ -1,6 +1,6 @@
from .widgets import JupyterChart from .widgets import JupyterChart
from .util import ( from .util import (
is_vbt_indicator is_vbt_indicator, get_next_color
) )
import pandas as pd import pandas as pd
@ -32,7 +32,8 @@ class Panel:
Attributes Attributes
---------- ----------
* ohlcv : tuple optional\n * ohlcv : tuple optional\n
(series, entries, exits, other_markers) (series, entries, exits, other_markers).\n
entries, exits and other_markers can be sr/df or lists of sr/df or list of tuples (if you want to specify color)
* histogram : list of tuples, optional.\n * histogram : list of tuples, optional.\n
[(series, name, color, opacity)] [(series, name, color, opacity)]
* title : str, optional * title : str, optional
@ -83,6 +84,26 @@ class Panel:
) )
ch = chart([pane1, pane2], sync=True, title="neco", size="m", xloc=slice("2024-02-12 09:30","2024-02-12 16:00")) ch = chart([pane1, pane2], sync=True, title="neco", size="m", xloc=slice("2024-02-12 09:30","2024-02-12 16:00"))
#Markers examples
#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")
#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)", 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, ohlcv=None, right=None, left=None, middle1=None, middle2=None, histogram=None, title=None, xloc=None, precision=None):
@ -97,7 +118,7 @@ class Panel:
self.precision = precision self.precision = precision
def chart(panes: list[Panel], sync=False, title='', size="m", xloc=None, session: str="9:30", precision=None): def chart(panes: list[Panel], sync=False, title='', size="m", xloc=None, session: str="9:30:00", precision=None):
""" """
Function to fast render a chart with multiple panes. This function manipulates graphical 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 output or interfaces with an external framework to display charts with synchronized
@ -177,13 +198,14 @@ def chart(panes: list[Panel], sync=False, title='', size="m", xloc=None, session
return dfsr.vbt.xloc[xloc].obj return dfsr.vbt.xloc[xloc].obj
size_to_dimensions = { size_to_dimensions = {
'xs': (600, 300), 'xs': (600, 200),
's': (800, 400), 's': (800, 400),
'm': (1000, 600), 'm': (1000, 600),
'l': (1300, 800)} 'l': (1300, 800)}
width, height = size_to_dimensions.get(size, (1000, 600)) width, height = size_to_dimensions.get(size, (1000, 600))
height_ratio = 1 / len(panes) height_ratio = 1 / len(panes)
main_title_set = False main_title_set = False
output_series = None
for index, pane in enumerate(panes): for index, pane in enumerate(panes):
subchartX = None subchartX = None
if index == 0: if index == 0:
@ -195,15 +217,30 @@ def chart(panes: list[Panel], sync=False, title='', size="m", xloc=None, session
xloc = pane.xloc if pane.xloc is not None else xloc xloc = pane.xloc if pane.xloc is not None else xloc
if pane.ohlcv is not None: def display_markers(active_chart: JupyterChart, markers, type= None, xloc=None):
color = None
if isinstance(markers, list):
for markerset in markers:
if isinstance(markerset, tuple):
markerset,color = markerset
active_chart.markers_set(markers=xloc_me(markerset, xloc), type=type, color=color if color is not None else None)
else:
if isinstance(markers, tuple):
markers,color = markers
active_chart.markers_set(markers=xloc_me(markers, xloc), type=type, color=color if color is not None else None)
if pane.ohlcv != ():
series, entries, exits, markers = (pane.ohlcv + (None,) * 4)[:4] series, entries, exits, markers = (pane.ohlcv + (None,) * 4)[:4]
if series is None:
raise ValueError("OHLCV series cannot be None. Omit the OHLCV tuple.")
active_chart.set(xloc_me(series, xloc)) active_chart.set(xloc_me(series, xloc))
if entries is not None: if entries is not None:
active_chart.markers_set(xloc_me(entries, xloc), "entries") display_markers(active_chart=active_chart, markers=entries, type="entries", xloc=xloc)
if exits is not None: if exits is not None:
active_chart.markers_set(xloc_me(exits, xloc), "exits") display_markers(active_chart=active_chart, markers=exits, type="exits", xloc=xloc)
if markers is not None: if markers is not None:
active_chart.markers_set(xloc_me(markers, xloc)) display_markers(chart=active_chart, markers=markers, xloc=xloc)
for tup in pane.histogram: for tup in pane.histogram:
series, name, color, opacity, _ = (tup + (None, None, None, None, None))[:5] series, name, color, opacity, _ = (tup + (None, None, None, None, None))[:5]
@ -235,7 +272,8 @@ def chart(panes: list[Panel], sync=False, title='', size="m", xloc=None, session
if is_vbt_indicator(series): if is_vbt_indicator(series):
series = series.xloc[xloc] if xloc is not None else series series = series.xloc[xloc] if xloc is not None else series
for output in series.output_names: for output in series.output_names:
output_series = getattr(series, output) 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 = active_chart.create_line(name=output, priceScaleId=att_name)#, color="blue")
tmp.set(output_series) tmp.set(output_series)
else: else:
@ -249,16 +287,17 @@ def chart(panes: list[Panel], sync=False, title='', size="m", xloc=None, session
tmp.precision(pane.precision if pane.precision is not None else precision) tmp.precision(pane.precision if pane.precision is not None else precision)
if entries is not None: if entries is not None:
tmp.markers_set(xloc_me(entries, xloc), "entries") display_markers(active_chart=tmp, markers=entries, type="entries", xloc=xloc)
if exits is not None: if exits is not None:
tmp.markers_set(xloc_me(exits, xloc), "exits") display_markers(active_chart=tmp, markers=exits, type="exits", xloc=xloc)
if markers is not None: if markers is not None:
tmp.markers_set(xloc_me(markers, xloc)) display_markers(active_chart=tmp, markers=markers, xloc=xloc)
active_chart.legend(True) active_chart.legend(True)
active_chart.fit() active_chart.fit()
if session is not None and session: if session is not None and session:
active_chart.vertical_span(start_time=xloc_me(series, xloc).vbt.xloc[session].obj.index.to_list(), color="rgba(252, 255, 187, 0.42)") 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)")
if not main_title_set: if not main_title_set:
chartX.topbar.textbox("title",title) chartX.topbar.textbox("title",title)

View File

@ -8,6 +8,72 @@ import pandas as pd
import re import re
from matplotlib.colors import to_rgba from matplotlib.colors import to_rgba
# # Predefined colors that stand out well on dark backgrounds
# COLORS = [
# 'rgba(255, 0, 0, 0.6)', # Red
# 'rgba(0, 255, 0, 0.6)', # Green
# 'rgba(0, 0, 255, 0.6)', # Blue
# 'rgba(255, 255, 0, 0.6)', # Yellow
# 'rgba(255, 165, 0, 0.6)', # Orange
# 'rgba(75, 0, 130, 0.6)', # Indigo
# 'rgba(238, 130, 238, 0.6)', # Violet
# 'rgba(0, 255, 255, 0.6)', # Cyan
# 'rgba(255, 192, 203, 0.6)', # Pink
# 'rgba(0, 128, 128, 0.6)', # Teal
# 'rgba(128, 0, 128, 0.6)', # Purple
# 'rgba(255, 215, 0, 0.6)', # Gold
# 'rgba(173, 255, 47, 0.6)', # Green Yellow
# ]
# def get_next_color():
# return random.choice(COLORS)
# Predefined pool of colors
COLORS = [
"#63AA57", "#8F8AB0", "#E24AEE", "#D06AA6", "#7891BA", "#A39A34", "#8A94A2", "#61BB2F",
"#FD569D", "#1EB6E1", "#379AC9", "#FD6F2E", "#8C9858", "#39A4A3", "#6D97F4", "#1ECB01", "#FA5B16", "#A6891C",
"#48CF10", "#D27B26", "#D56B55", "#FE3AB8", "#E35C51", "#EC4FE6", "#E250A3", "#BA618E", "#1BC074", "#C57784",
"#888BC5", "#4FA452", "#80885C", "#B97272", "#33BF98", "#B7961D", "#A07284", "#02E54E", "#AF7F35", "#F852EF",
"#6D955B", "#E0676E", "#F73DEC", "#CE53FD", "#9773D3", "#649E81", "#D062CE", "#AB73E7", "#A4729C", "#E76A07",
"#E85CCB", "#A16FB1", "#4BB859", "#B25EE2", "#8580CE", "#A275EF", "#AC9245", "#4D988D", "#B672C9", "#4CA96E",
"#C9873E", "#5BB147", "#10C783", "#D7647D", "#CB893A", "#A586BA", "#28C0A2", "#61A755", "#0EB7C5", "#2DADBC",
"#17BB71", "#2BC733", "#2BB890", "#F04EF8", "#699580", "#A88809", "#EB3FF6", "#A75ED3", "#859171", "#BB6285",
"#81A147", "#AD7CD2", "#65B630", "#C9616C", "#BD5EFA", "#7A9F30", "#2AB6AB", "#FC496A", "#687FC7", "#DB40E7",
"#07BCE9", "#509F63", "#EC4FDD", "#A079BE", "#C17297", "#E447C2", "#E95AD9", "#9FA01E", "#7E86CF", "#21E316",
"#1CABF9", "#17C24F", "#9C9254", "#C97994", "#4BA9DA", "#0DD595", "#13BEA8", "#C2855D", "#DF6C13", "#60B370",
"#0FC3F6", "#C1830E", "#3AC917", "#0EBBB0", "#CC50B4", "#B768EC", "#D47F49", "#B47BC5", "#38ADBD", "#05DC53",
"#44CD4E", "#838E65", "#49D70F", "#2DADBE", "#2CB0C9", "#DA703E", "#06B5CA", "#7BAF3E", "#918E79", "#2AA5E5",
"#C37F5E", "#07B8C9", "#4CBA27", "#E752C6", "#7F93B2", "#4798CD", "#45AA4C", "#4DB666", "#7683A7", "#758685",
"#4B9FAD", "#9280FD", "#6682DD", "#42ACBE", "#C1609F", "#D850DB", "#649A62", "#54CC22", "#AD81C1", "#BF7A43",
"#0FCEA5", "#D06DAF", "#87799B", "#4DA94E", "#2FD654", "#07D587", "#21CF0C", "#03CF34", "#42C771", "#D563CD",
"#6D9E9A", "#C76C59", "#68B368", "#11BCE5", "#0DCFB3", "#9266D8", "#BF67F6", "#88A04E", "#73BE17", "#67B437",
"#8586E4", "#9F8749", "#479CA5", "#CC777E", "#4FAF46", "#9D9836", "#918DAF", "#D167B8", "#6F9DA5", "#2BB167",
"#16B8BC", "#B4861F", "#A08487", "#67B357", "#5CAA5C", "#20CA49", "#D18813", "#15D63F", "#C8618F", "#887E92",
"#21C457", "#4EA8CE", "#53BE49", "#5A86D5", "#BD7E4E", "#27B0A1", "#33CF42", "#709083", "#38A8DE", "#4CA762",
"#1EA4FF", "#DE3EE4", "#70A860", "#39A3C8", "#6BBB39", "#F053F4", "#8C7FB5", "#969F21", "#B19841", "#E57148",
"#C25DA7", "#6DA979", "#B27D73", "#7F9786", "#41AC99", "#C58848", "#948F9E", "#6BB620", "#81AB3B", "#09DE44",
"#43A9D2", "#41B0D7", "#20ACAA", "#649FCB", "#CD8345", "#A88669", "#3EA5E7", "#F36A19", "#E06B48", "#8388BD",
"#EC6153", "#639082", "#52CA32", "#878BAA", "#02BCDB", "#828FD9", "#3DC07F", "#29D46A", "#9C7CC1", "#EB7713",
"#F95F6A", "#E25F4C", "#589994", "#D45AB7", "#DE66AB", "#B8715F", "#E850F4", "#FB6420", "#C2832C", "#6383C5",
"#D57A58", "#EF652C", "#02D71A", "#ED664D", "#60A526"
]
# Iterator to keep track of the current color index
color_index = 0
def get_next_color():
global color_index
# Get the next color from the list
color = COLORS[color_index]
# Convert the color from HEX to RGBA format
color_index = (color_index + 1) % len(COLORS)
return hex_to_rgba(color)
def hex_to_rgba(hex_color, alpha=0.5):
hex_color = hex_color.lstrip('#')
r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
return f'rgba({r}, {g}, {b}, {alpha})'
def apply_opacity(color, opacity): def apply_opacity(color, opacity):
""" """
Converts any color format (named, hex, RGB, or RGBA) to RGBA format and applies a specified opacity. Converts any color format (named, hex, RGB, or RGBA) to RGBA format and applies a specified opacity.

View File

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