From 3a859c97fc547c79f3c764352f8e18535f3641bb Mon Sep 17 00:00:00 2001 From: David Brazda Date: Thu, 27 Jun 2024 13:47:12 +0200 Subject: [PATCH] support for markers list --- lightweight_charts/abstract.py | 74 ++-------------------------------- lightweight_charts/helpers.py | 65 +++++++++++++++++++++++------ lightweight_charts/util.py | 66 ++++++++++++++++++++++++++++++ setup.py | 2 +- 4 files changed, 123 insertions(+), 84 deletions(-) diff --git a/lightweight_charts/abstract.py b/lightweight_charts/abstract.py index d10afde..4ab9d13 100644 --- a/lightweight_charts/abstract.py +++ b/lightweight_charts/abstract.py @@ -14,78 +14,12 @@ from .topbar import TopBar from .util import ( BulkRunScript, Pane, Events, IDGen, as_enum, jbool, js_json, TIME, NUM, FLOAT, 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__)) 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: _id_gen = IDGen() handlers = {} @@ -336,7 +270,7 @@ class SeriesCommon(Pane): col_name: Optional[str] = None, position: MARKER_POSITION = 'below', shape: MARKER_SHAPE = 'arrow_up', - color: str = '#2196F3', text: str = ''): + color: str = None, text: str = ''): """ Adds multiple markers from pd series or Dataframe :param markers: A pandas Series or Dataframe with DateTimeIndex and boolean values. @@ -356,11 +290,11 @@ class SeriesCommon(Pane): case "entries": position = "below" shape = "arrow_up" - color = "blue" + color = "blue" if color is None else color case "exits": position = "above" shape = "arrow_down" - color = "red" + color = "red" if color is None else color if isinstance(markers, pd.Series): markers = markers.to_frame(name="markers") diff --git a/lightweight_charts/helpers.py b/lightweight_charts/helpers.py index 184d186..acb32ef 100644 --- a/lightweight_charts/helpers.py +++ b/lightweight_charts/helpers.py @@ -1,6 +1,6 @@ from .widgets import JupyterChart from .util import ( - is_vbt_indicator + is_vbt_indicator, get_next_color ) import pandas as pd @@ -32,7 +32,8 @@ class Panel: Attributes ---------- * 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 [(series, name, color, opacity)] * 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")) + + #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): @@ -97,7 +118,7 @@ class Panel: 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 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 size_to_dimensions = { - 'xs': (600, 300), + 'xs': (600, 200), 's': (800, 400), 'm': (1000, 600), 'l': (1300, 800)} width, height = size_to_dimensions.get(size, (1000, 600)) height_ratio = 1 / len(panes) main_title_set = False + output_series = None for index, pane in enumerate(panes): subchartX = None 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 - 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] + if series is None: + raise ValueError("OHLCV series cannot be None. Omit the OHLCV tuple.") active_chart.set(xloc_me(series, xloc)) 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: - 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: - active_chart.markers_set(xloc_me(markers, xloc)) + display_markers(chart=active_chart, markers=markers, xloc=xloc) for tup in pane.histogram: 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): series = series.xloc[xloc] if xloc is not None else series 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.set(output_series) 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) 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: - tmp.markers_set(xloc_me(exits, xloc), "exits") + display_markers(active_chart=tmp, markers=exits, type="exits", xloc=xloc) 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.fit() 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: chartX.topbar.textbox("title",title) diff --git a/lightweight_charts/util.py b/lightweight_charts/util.py index 1b9f2a0..93f4082 100644 --- a/lightweight_charts/util.py +++ b/lightweight_charts/util.py @@ -8,6 +8,72 @@ import pandas as pd import re 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): """ Converts any color format (named, hex, RGB, or RGBA) to RGBA format and applies a specified opacity. diff --git a/setup.py b/setup.py index bee7dbd..9292db9 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ with open('README.md', 'r', encoding='utf-8') as f: setup( name='lightweight_charts', - version='2.1.3', + version='2.1.4', packages=find_packages(), python_requires='>=3.8', install_requires=[