From edd0b8b93ca0155640a3a13183d38510291e456a Mon Sep 17 00:00:00 2001 From: Felix Meier Date: Sat, 23 Oct 2021 11:08:02 +0200 Subject: [PATCH 1/7] adding a VolumeProfile to the plotting new features: - volumeProfile on the right side of the plot - separating the Volume and volumeProfile in seperate buy and sell trades --- docs/plotting.md | 6 ++ freqtrade/plot/plotting.py | 134 +++++++++++++++++++++++++++++++++---- 2 files changed, 128 insertions(+), 12 deletions(-) diff --git a/docs/plotting.md b/docs/plotting.md index 9fae38504..ed20da452 100644 --- a/docs/plotting.md +++ b/docs/plotting.md @@ -211,6 +211,12 @@ Sample configuration with inline comments explaining the process: "RSI": { 'rsi': {'color': 'red'} } + }, + 'volume': { + 'showBuySell': 'true', + 'showVolumeProfile': 'true', + # 'VolumeProfileHistoryBars': 96, # default: all + # 'VolumeProfilePriceRangeSplices': 100 # default: 50 } } diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 509c03e90..fa264b840 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -340,6 +340,51 @@ def add_areas(fig, row: int, data: pd.DataFrame, indicators) -> make_subplots: return fig +def createVolumeProfileData(df: pd.DataFrame, bar_split: int = 50, + history_bars: int = None) -> pd.DataFrame: + """ + Generate the Volume Profile Date for the given dataframe + :param df: DataFrame with candle data + :param bar_split: in how many bars should the PriceRange be spit (default=50) + :param history_bars: how many history bars should be considered (default=all) + :return: DataFrame with Price/Trade Date + """ + + df_vol = pd.DataFrame(columns=['price_lower', 'price_upper', + 'price_avg', 'volume', 'volume_buy', 'volume_sell']) + + div = df['close'].max() - df['close'].min() + step = div/bar_split + for i in range(0, bar_split): + # finding the priceRange + price_lower = df['close'].min() + step*i + price_upper = df['close'].min() + step*(i+1) + price_avg = (price_lower + price_upper)/2 + + # the volume for the given priceRange + if history_bars is not None: + dt_tail = df.tail(history_bars) + else: + dt_tail = df + + volume_comb = dt_tail[(dt_tail['close'] >= price_lower) & ( + dt_tail['close'] < price_upper)]['volume'].sum() + + volume_buy = dt_tail[(dt_tail['close'] >= price_lower) & ( + dt_tail['close'] < price_upper)]['volume_buy'].sum() + + volume_sell = dt_tail[(dt_tail['close'] >= price_lower) & ( + dt_tail['close'] < price_upper)]['volume_sell'].sum() + + df_vol = df_vol.append({'price_lower': price_lower, + 'price_upper': price_upper, + 'price_avg': price_avg, + 'volume': volume_comb, + 'volume_buy': volume_buy, + 'volume_sell': volume_sell}, ignore_index=True) + return df_vol + + def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFrame = None, *, indicators1: List[str] = [], indicators2: List[str] = [], @@ -362,16 +407,21 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra # Define the graph fig = make_subplots( rows=rows, - cols=1, + cols=2, # ToDo: Check if 2 columns (instead of one) cause any issues somewhere else + # ToDo: when showVolumeProfile==false, don't show the second column + column_widths=[8, 1], # set the width of the Volume Profile shared_xaxes=True, + shared_yaxes=True, row_width=row_widths + [1, 4], vertical_spacing=0.0001, + horizontal_spacing=0.0001, ) fig['layout'].update(title=pair) fig['layout']['yaxis1'].update(title='Price') - fig['layout']['yaxis2'].update(title='Volume') + fig['layout']['yaxis3'].update(title='Volume') for i, name in enumerate(plot_config['subplots']): - fig['layout'][f'yaxis{3 + i}'].update(title=name) + fig['layout'][f'yaxis{5 + i }'].update(title=name) + # ToDo: if more then one subplot is used, the title is not shown fig['layout']['xaxis']['rangeslider'].update(visible=False) fig.update_layout(modebar_add=["v1hovermode", "toggleSpikeLines"]) @@ -436,15 +486,75 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra fig = add_indicators(fig=fig, row=1, indicators=plot_config['main_plot'], data=data) fig = add_areas(fig, 1, data, plot_config['main_plot']) fig = plot_trades(fig, trades) - # sub plot: Volume goes to row 2 - volume = go.Bar( - x=data['date'], - y=data['volume'], - name='Volume', - marker_color='DarkSlateGrey', - marker_line_color='DarkSlateGrey' - ) - fig.add_trace(volume, 2, 1) + + # VolumeProfile + volume_config = plot_config['volume'] if 'volume' in plot_config else {} + showBuySell = volume_config['showBuySell'] if 'showBuySell' in volume_config else 'false' + showVolumeProfile = volume_config['showVolumeProfile'] if 'showVolumeProfile' in \ + volume_config else 'false' + VolumeProfileHistoryBars = volume_config['VolumeProfileHistoryBars'] if \ + 'VolumeProfileHistoryBars' in volume_config else -1 + VolumeProfilePriceRangeSplices = volume_config[ + 'VolumeProfilePriceRangeSplices'] if 'VolumeProfilePriceRangeSplices' in \ + volume_config else 50 + + if showVolumeProfile.lower() == 'true': + volumeProfileData = createVolumeProfileData( + data, VolumeProfilePriceRangeSplices, VolumeProfileHistoryBars) + + volume_buy = go.Bar( + x=volumeProfileData['volume_buy'], + y=volumeProfileData['price_avg'], + name='VolumeProfile Buy', + marker_color='Green', + marker_line_color='Green', + orientation='h', + ) + volume_sell = go.Bar( + x=volumeProfileData['volume_sell'], + y=volumeProfileData['price_avg'], + name='VolumeProfile Sell', + marker_color='Red', + marker_line_color='Red', + orientation='h', + ) + + fig.add_trace(volume_sell, 1, 2) + fig.add_trace(volume_buy, 1, 2) + fig.update_layout(barmode='stack') + + # standard volume plot + if showBuySell.lower() == 'true': # show volume plot split by sell & buy trades + volume_red = go.Bar( + x=data['date'], + y=data['volume'] * (data['high']-data['close']) / (data['high']-data['low']), + name='Volume Sell', + marker_color='Red', + marker_line_color='Red' + ) + fig.add_trace(volume_red, 2, 1) + + volume_green = go.Bar( + x=data['date'], + + y=data['volume'] * (data['close']-data['low']) / (data['high']-data['low']), + name='Volume Buy', + marker_color='Green', + marker_line_color='Green' + ) + fig.add_trace(volume_green, 2, 1) + + else: # show 'normal' gray volume plot + # sub plot: Volume goes to row 2 + volume = go.Bar( + x=data['date'], + y=data['volume'], + name='Volume', + marker_color='DarkSlateGrey', + marker_line_color='DarkSlateGrey' + ) + fig.add_trace(volume, 2, 1) + # add each sub plot to a separate row for i, label in enumerate(plot_config['subplots']): sub_config = plot_config['subplots'][label] From 02007b5e5d64b6e136873ff0992f07f12daf7cbe Mon Sep 17 00:00:00 2001 From: Felix Meier Date: Sat, 23 Oct 2021 17:52:30 +0200 Subject: [PATCH 2/7] hiding the volumeProfile plot-column if showVolumeProfile is false --- freqtrade/plot/plotting.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index fa264b840..1b4914b0a 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -404,12 +404,24 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra plot_config = create_plotconfig(indicators1, indicators2, plot_config) rows = 2 + len(plot_config['subplots']) row_widths = [1 for _ in plot_config['subplots']] + + # load VolumeProfile configurations + volume_config = plot_config['volume'] if 'volume' in plot_config else {} + showBuySell = volume_config['showBuySell'] if 'showBuySell' in volume_config else 'false' + showVolumeProfile = volume_config['showVolumeProfile'] if 'showVolumeProfile' in \ + volume_config else 'false' + VolumeProfileHistoryBars = volume_config['VolumeProfileHistoryBars'] if \ + 'VolumeProfileHistoryBars' in volume_config else -1 + VolumeProfilePriceRangeSplices = volume_config[ + 'VolumeProfilePriceRangeSplices'] if 'VolumeProfilePriceRangeSplices' in \ + volume_config else 50 + # Define the graph fig = make_subplots( rows=rows, cols=2, # ToDo: Check if 2 columns (instead of one) cause any issues somewhere else - # ToDo: when showVolumeProfile==false, don't show the second column - column_widths=[8, 1], # set the width of the Volume Profile + # set the width of the Volume Profile + column_widths=[8, 1 if showVolumeProfile.lower() == 'true' else 0], shared_xaxes=True, shared_yaxes=True, row_width=row_widths + [1, 4], @@ -488,15 +500,6 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra fig = plot_trades(fig, trades) # VolumeProfile - volume_config = plot_config['volume'] if 'volume' in plot_config else {} - showBuySell = volume_config['showBuySell'] if 'showBuySell' in volume_config else 'false' - showVolumeProfile = volume_config['showVolumeProfile'] if 'showVolumeProfile' in \ - volume_config else 'false' - VolumeProfileHistoryBars = volume_config['VolumeProfileHistoryBars'] if \ - 'VolumeProfileHistoryBars' in volume_config else -1 - VolumeProfilePriceRangeSplices = volume_config[ - 'VolumeProfilePriceRangeSplices'] if 'VolumeProfilePriceRangeSplices' in \ - volume_config else 50 if showVolumeProfile.lower() == 'true': volumeProfileData = createVolumeProfileData( From 848d2d87853337380a3f1d9b6d2d766ec23f0c59 Mon Sep 17 00:00:00 2001 From: Felix Meier Date: Sat, 23 Oct 2021 18:08:43 +0200 Subject: [PATCH 3/7] fix to show the title with more than one subplot --- freqtrade/plot/plotting.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 1b4914b0a..bcfae89c6 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -432,8 +432,7 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra fig['layout']['yaxis1'].update(title='Price') fig['layout']['yaxis3'].update(title='Volume') for i, name in enumerate(plot_config['subplots']): - fig['layout'][f'yaxis{5 + i }'].update(title=name) - # ToDo: if more then one subplot is used, the title is not shown + fig['layout'][f'yaxis{5 + i*2 }'].update(title=name) fig['layout']['xaxis']['rangeslider'].update(visible=False) fig.update_layout(modebar_add=["v1hovermode", "toggleSpikeLines"]) From 6f1604f72c0b015b335ebdb39b0b3ffe6b3db21c Mon Sep 17 00:00:00 2001 From: Felix Meier Date: Mon, 25 Oct 2021 18:28:26 +0200 Subject: [PATCH 4/7] generating sell/buy volumes in plotting.py itself adding nicer colors --- freqtrade/plot/plotting.py | 48 +++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index bcfae89c6..1b95f1a31 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -16,6 +16,12 @@ from freqtrade.misc import pair_to_filename from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.strategy import IStrategy +# ToDo: +# generate the buy/Sell data here and not in the strategy to make it independet +# show just the VolumeProfile for the displayed area +# idea: bind the y-axis of the volume profile to the x-axis of the candels and see if +# it is possible to groups and sum the (buy/sell)-volume together +# adding unit tests logger = logging.getLogger(__name__) @@ -340,7 +346,26 @@ def add_areas(fig, row: int, data: pd.DataFrame, indicators) -> make_subplots: return fig -def createVolumeProfileData(df: pd.DataFrame, bar_split: int = 50, +def generateBuySellVolumes(dataframe) -> pd.DataFrame: + candles = dataframe.copy() + + candles['volume_buy'] = 0 + candles['volume_sell'] = 0 + + for i in range(len(candles)): + + candles['volume_buy'].iat[i] = candles['volume'].iat[i] * \ + (candles['close'].iat[i]-candles['low'].iat[i]) / \ + (candles['high'].iat[i]-candles['low'].iat[i]) + + candles['volume_sell'].iat[i] = candles['volume'].iat[i] * \ + (candles['high'].iat[i]-candles['close'].iat[i]) / \ + (candles['high'].iat[i]-candles['low'].iat[i]) + + return candles + + +def createVolumeProfileData(data: pd.DataFrame, bar_split: int = 50, history_bars: int = None) -> pd.DataFrame: """ Generate the Volume Profile Date for the given dataframe @@ -349,6 +374,10 @@ def createVolumeProfileData(df: pd.DataFrame, bar_split: int = 50, :param history_bars: how many history bars should be considered (default=all) :return: DataFrame with Price/Trade Date """ + df = data.copy() + + df["volume_buy"] = generateBuySellVolumes(df)["volume_buy"] + df["volume_sell"] = generateBuySellVolumes(df)["volume_sell"] df_vol = pd.DataFrame(columns=['price_lower', 'price_upper', 'price_avg', 'volume', 'volume_buy', 'volume_sell']) @@ -501,6 +530,7 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra # VolumeProfile if showVolumeProfile.lower() == 'true': + volumeProfileData = createVolumeProfileData( data, VolumeProfilePriceRangeSplices, VolumeProfileHistoryBars) @@ -508,16 +538,16 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra x=volumeProfileData['volume_buy'], y=volumeProfileData['price_avg'], name='VolumeProfile Buy', - marker_color='Green', - marker_line_color='Green', + marker_color='rgba(99, 146, 52, 0.6)', + marker_line_color='rgba(99, 146, 52, 0.6)', orientation='h', ) volume_sell = go.Bar( x=volumeProfileData['volume_sell'], y=volumeProfileData['price_avg'], name='VolumeProfile Sell', - marker_color='Red', - marker_line_color='Red', + marker_color='rgba(208, 41, 41, 0.6)', + marker_line_color='rgba(208, 41, 41, 0.6)', orientation='h', ) @@ -531,8 +561,8 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra x=data['date'], y=data['volume'] * (data['high']-data['close']) / (data['high']-data['low']), name='Volume Sell', - marker_color='Red', - marker_line_color='Red' + marker_color='rgba(208, 41, 41, 0.6)', + marker_line_color='rgba(208, 41, 41, 0.6)' ) fig.add_trace(volume_red, 2, 1) @@ -541,8 +571,8 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra y=data['volume'] * (data['close']-data['low']) / (data['high']-data['low']), name='Volume Buy', - marker_color='Green', - marker_line_color='Green' + marker_color='rgba(99, 146, 52, 0.6)', + marker_line_color='rgba(99, 146, 52, 0.6)' ) fig.add_trace(volume_green, 2, 1) From 0d995038e177546de00e1144daa6a4739f2d9b80 Mon Sep 17 00:00:00 2001 From: Felix Meier Date: Mon, 25 Oct 2021 20:17:52 +0200 Subject: [PATCH 5/7] adding some basic unittests adding stackmode when VolumeProfile disabled --- freqtrade/plot/plotting.py | 37 +++++++++------ tests/test_plotting.py | 92 +++++++++++++++++++++++++++++++++++++- 2 files changed, 113 insertions(+), 16 deletions(-) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 1b95f1a31..a3e27a742 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -16,12 +16,13 @@ from freqtrade.misc import pair_to_filename from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.strategy import IStrategy -# ToDo: -# generate the buy/Sell data here and not in the strategy to make it independet -# show just the VolumeProfile for the displayed area -# idea: bind the y-axis of the volume profile to the x-axis of the candels and see if -# it is possible to groups and sum the (buy/sell)-volume together -# adding unit tests + + +# Possible later improvement: +# just show the volume profile for the zoomed-in area: +# currently the volume profile is always based on all data in the plot +# check if it is possible to bind the y-axis of the volume profile to the x-axis +# of the candels and group by price-read and sum the (buy/sell)-volume together logger = logging.getLogger(__name__) @@ -261,7 +262,14 @@ def create_plotconfig(indicators1: List[str], indicators2: List[str], :return: plot_config - eventually with indicators 1 and 2 """ - if plot_config: + if plot_config and 'main_plot' not in plot_config and 'subplots' not in plot_config: + # if just the volumeProfile config is set but main_plot and subplots are empty + indicators1 = ['sma', 'ema3', 'ema5'] + plot_config['main_plot'] = {ind: {} for ind in indicators1} + indicators2 = ['macd', 'macdsignal'] + plot_config['subplots'] = {'Other': {ind: {} for ind in indicators2}} + + elif plot_config: if indicators1: plot_config['main_plot'] = {ind: {} for ind in indicators1} if indicators2: @@ -354,13 +362,13 @@ def generateBuySellVolumes(dataframe) -> pd.DataFrame: for i in range(len(candles)): - candles['volume_buy'].iat[i] = candles['volume'].iat[i] * \ - (candles['close'].iat[i]-candles['low'].iat[i]) / \ - (candles['high'].iat[i]-candles['low'].iat[i]) + candles['volume_buy'].iat[i] = (candles['volume'].iat[i] * + (candles['close'].iat[i]-candles['low'].iat[i]) / + (candles['high'].iat[i]-candles['low'].iat[i])) if (candles['high'].iat[i]-candles['low'].iat[i]) > 0 else 0 - candles['volume_sell'].iat[i] = candles['volume'].iat[i] * \ - (candles['high'].iat[i]-candles['close'].iat[i]) / \ - (candles['high'].iat[i]-candles['low'].iat[i]) + candles['volume_sell'].iat[i] = (candles['volume'].iat[i] * + (candles['high'].iat[i]-candles['close'].iat[i]) / + (candles['high'].iat[i]-candles['low'].iat[i])) if (candles['high'].iat[i]-candles['low'].iat[i]) > 0 else 0 return candles @@ -574,7 +582,8 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra marker_color='rgba(99, 146, 52, 0.6)', marker_line_color='rgba(99, 146, 52, 0.6)' ) - fig.add_trace(volume_green, 2, 1) + fig.add_trace(volume_green, 2, 1,) + fig.update_layout(barmode='stack') else: # show 'normal' gray volume plot # sub plot: Volume goes to row 2 diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 51301a464..0d45502b1 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -13,9 +13,9 @@ from freqtrade.configuration import TimeRange from freqtrade.data import history from freqtrade.data.btanalysis import create_cum_profit, load_backtest_data from freqtrade.exceptions import OperationalException -from freqtrade.plot.plotting import (add_areas, add_indicators, add_profit, create_plotconfig, +from freqtrade.plot.plotting import (add_areas, add_indicators, add_profit, create_plotconfig, createVolumeProfileData, generate_candlestick_graph, generate_plot_filename, - generate_profit_graph, init_plotscript, load_and_plot_trades, + generate_profit_graph, generateBuySellVolumes, init_plotscript, load_and_plot_trades, plot_profit, plot_trades, store_plot_file) from freqtrade.resolvers import StrategyResolver from tests.conftest import get_args, log_has, log_has_re, patch_exchange @@ -61,6 +61,33 @@ def test_init_plotscript(default_conf, mocker, testdatadir): assert "ADA/BTC" in ret["ohlcv"] +def test_generate_volumeProfile(default_conf, testdatadir): + pair = "UNITTEST/BTC" + timerange = TimeRange(None, 'line', 0, -1000) + + data = history.load_pair_history(pair=pair, timeframe='1m', + datadir=testdatadir, timerange=timerange) + strategy = StrategyResolver.load_strategy(default_conf) + + data = strategy.analyze_ticker(data, {'pair': pair}) + + buysell_volumes = generateBuySellVolumes(data) + + assert buysell_volumes['volume_buy'].any() + assert buysell_volumes['volume_sell'].any() + + volumeProfile = createVolumeProfileData(buysell_volumes, 50, 100) + + assert volumeProfile['price_lower'].any() + assert volumeProfile['price_upper'].any() + assert volumeProfile['price_avg'].any() + assert volumeProfile['volume'].any() + assert volumeProfile['volume_buy'].any() + assert volumeProfile['volume_sell'].any() + + assert len(volumeProfile.index) == 50 + + def test_add_indicators(default_conf, testdatadir, caplog): pair = "UNITTEST/BTC" timerange = TimeRange(None, 'line', 0, -1000) @@ -274,6 +301,47 @@ def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir) assert trades_mock.call_count == 1 +def test_generate_candlestick_graph_withVolumeProfile(default_conf, mocker, testdatadir, caplog): + row_mock = mocker.patch('freqtrade.plot.plotting.add_indicators', + MagicMock(side_effect=fig_generating_mock)) + trades_mock = mocker.patch('freqtrade.plot.plotting.plot_trades', + MagicMock(side_effect=fig_generating_mock)) + + pair = "UNITTEST/BTC" + timerange = TimeRange(None, 'line', 0, -1000) + data = history.load_pair_history(pair=pair, timeframe='1m', + datadir=testdatadir, timerange=timerange) + data['buy'] = 0 + data['sell'] = 0 + + indicators1 = [] + indicators2 = [] + fig = generate_candlestick_graph(pair=pair, data=data, trades=None, + plot_config={'main_plot': {'sma': {}, 'ema200': {}}, + 'subplots': {'Other': {'macdsignal': {}}}, + 'volume': {'showBuySell': 'true', 'showVolumeProfile': 'true', 'VolumeProfileHistoryBars': 96, 'VolumeProfilePriceRangeSplices': 60}}) + assert isinstance(fig, go.Figure) + assert fig.layout.title.text == pair + figure = fig.layout.figure + + assert len(figure.data) == 5 + # Candlesticks are plotted first + candles = find_trace_in_fig_data(figure.data, "Price") + assert isinstance(candles, go.Candlestick) + + assert isinstance(find_trace_in_fig_data(figure.data, "Volume Sell"), go.Bar) + assert isinstance(find_trace_in_fig_data(figure.data, "Volume Buy"), go.Bar) + + assert isinstance(find_trace_in_fig_data(figure.data, "VolumeProfile Sell"), go.Bar) + assert isinstance(find_trace_in_fig_data(figure.data, "VolumeProfile Buy"), go.Bar) + + assert row_mock.call_count == 2 + assert trades_mock.call_count == 1 + + assert log_has("No buy-signals found.", caplog) + assert log_has("No sell-signals found.", caplog) + + def test_generate_Plot_filename(): fn = generate_plot_filename("UNITTEST/BTC", "5m") assert fn == "freqtrade-plot-UNITTEST_BTC-5m.html" @@ -469,6 +537,7 @@ def test_plot_profit(default_conf, mocker, testdatadir): ([], [], {}, {'main_plot': {'sma': {}, 'ema3': {}, 'ema5': {}}, 'subplots': {'Other': {'macd': {}, 'macdsignal': {}}}}), + # use indicators (['sma', 'ema3'], ['macd'], {}, {'main_plot': {'sma': {}, 'ema3': {}}, 'subplots': {'Other': {'macd': {}}}}), @@ -496,6 +565,25 @@ def test_plot_profit(default_conf, mocker, testdatadir): {'main_plot': {'sma': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}}, {'main_plot': {'sma': {}}, 'subplots': {'Other': {'macd': {}, 'macd_signal': {}}}} ), + # No indicators, use plot_conf with just volume profile + ([], [], + { + 'volume': {'showBuySell': 'true', 'showVolumeProfile': 'true', 'VolumeProfileHistoryBars': 96, 'VolumeProfilePriceRangeSplices': 100}, + }, + {'main_plot': {'sma': {}, 'ema3': {}, 'ema5': {}}, + 'subplots': {'Other': {'macd': {}, 'macdsignal': {}}}, + 'volume': {'showBuySell': 'true', 'showVolumeProfile': 'true', 'VolumeProfileHistoryBars': 96, 'VolumeProfilePriceRangeSplices': 100}}, + ), + # No indicators, use full plot_conf with volume profile + ([], [], + {'main_plot': {'sma': {}, 'ema200': {}}, + 'subplots': {'Other': {'macdsignal': {}}}, + 'volume': {'showBuySell': 'true', 'showVolumeProfile': 'true', 'VolumeProfileHistoryBars': 9, 'VolumeProfilePriceRangeSplices': 111}}, + {'main_plot': {'sma': {}, 'ema200': {}}, + 'subplots': {'Other': {'macdsignal': {}}}, + 'volume': {'showBuySell': 'true', 'showVolumeProfile': 'true', 'VolumeProfileHistoryBars': 9, 'VolumeProfilePriceRangeSplices': 111}}, + ) + ]) def test_create_plotconfig(ind1, ind2, plot_conf, exp): From 4f521618a684304df7f5acc0c930e98b11359f79 Mon Sep 17 00:00:00 2001 From: Felix Meier Date: Tue, 26 Oct 2021 00:02:26 +0200 Subject: [PATCH 6/7] fixing some flake8 tests --- freqtrade/plot/plotting.py | 6 ++++-- tests/test_plotting.py | 26 ++++++++++++++++---------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index a3e27a742..380c1535c 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -364,11 +364,13 @@ def generateBuySellVolumes(dataframe) -> pd.DataFrame: candles['volume_buy'].iat[i] = (candles['volume'].iat[i] * (candles['close'].iat[i]-candles['low'].iat[i]) / - (candles['high'].iat[i]-candles['low'].iat[i])) if (candles['high'].iat[i]-candles['low'].iat[i]) > 0 else 0 + (candles['high'].iat[i]-candles['low'].iat[i])) \ + if (candles['high'].iat[i]-candles['low'].iat[i]) > 0 else 0 candles['volume_sell'].iat[i] = (candles['volume'].iat[i] * (candles['high'].iat[i]-candles['close'].iat[i]) / - (candles['high'].iat[i]-candles['low'].iat[i])) if (candles['high'].iat[i]-candles['low'].iat[i]) > 0 else 0 + (candles['high'].iat[i]-candles['low'].iat[i])) \ + if (candles['high'].iat[i]-candles['low'].iat[i]) > 0 else 0 return candles diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 0d45502b1..2ada09ada 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -13,9 +13,10 @@ from freqtrade.configuration import TimeRange from freqtrade.data import history from freqtrade.data.btanalysis import create_cum_profit, load_backtest_data from freqtrade.exceptions import OperationalException -from freqtrade.plot.plotting import (add_areas, add_indicators, add_profit, create_plotconfig, createVolumeProfileData, - generate_candlestick_graph, generate_plot_filename, - generate_profit_graph, generateBuySellVolumes, init_plotscript, load_and_plot_trades, +from freqtrade.plot.plotting import (add_areas, add_indicators, add_profit, create_plotconfig, + createVolumeProfileData, generate_candlestick_graph, + generate_plot_filename, generate_profit_graph, + generateBuySellVolumes, init_plotscript, load_and_plot_trades, plot_profit, plot_trades, store_plot_file) from freqtrade.resolvers import StrategyResolver from tests.conftest import get_args, log_has, log_has_re, patch_exchange @@ -314,12 +315,13 @@ def test_generate_candlestick_graph_withVolumeProfile(default_conf, mocker, test data['buy'] = 0 data['sell'] = 0 - indicators1 = [] - indicators2 = [] fig = generate_candlestick_graph(pair=pair, data=data, trades=None, plot_config={'main_plot': {'sma': {}, 'ema200': {}}, 'subplots': {'Other': {'macdsignal': {}}}, - 'volume': {'showBuySell': 'true', 'showVolumeProfile': 'true', 'VolumeProfileHistoryBars': 96, 'VolumeProfilePriceRangeSplices': 60}}) + 'volume': {'showBuySell': 'true', + 'showVolumeProfile': 'true', + 'VolumeProfileHistoryBars': 96, + 'VolumeProfilePriceRangeSplices': 60}}) assert isinstance(fig, go.Figure) assert fig.layout.title.text == pair figure = fig.layout.figure @@ -568,20 +570,24 @@ def test_plot_profit(default_conf, mocker, testdatadir): # No indicators, use plot_conf with just volume profile ([], [], { - 'volume': {'showBuySell': 'true', 'showVolumeProfile': 'true', 'VolumeProfileHistoryBars': 96, 'VolumeProfilePriceRangeSplices': 100}, + 'volume': {'showBuySell': 'true', 'showVolumeProfile': 'true', + 'VolumeProfileHistoryBars': 96, 'VolumeProfilePriceRangeSplices': 100}, }, {'main_plot': {'sma': {}, 'ema3': {}, 'ema5': {}}, 'subplots': {'Other': {'macd': {}, 'macdsignal': {}}}, - 'volume': {'showBuySell': 'true', 'showVolumeProfile': 'true', 'VolumeProfileHistoryBars': 96, 'VolumeProfilePriceRangeSplices': 100}}, + 'volume': {'showBuySell': 'true', 'showVolumeProfile': 'true', + 'VolumeProfileHistoryBars': 96, 'VolumeProfilePriceRangeSplices': 100}}, ), # No indicators, use full plot_conf with volume profile ([], [], {'main_plot': {'sma': {}, 'ema200': {}}, 'subplots': {'Other': {'macdsignal': {}}}, - 'volume': {'showBuySell': 'true', 'showVolumeProfile': 'true', 'VolumeProfileHistoryBars': 9, 'VolumeProfilePriceRangeSplices': 111}}, + 'volume': {'showBuySell': 'true', 'showVolumeProfile': 'true', + 'VolumeProfileHistoryBars': 9, 'VolumeProfilePriceRangeSplices': 111}}, {'main_plot': {'sma': {}, 'ema200': {}}, 'subplots': {'Other': {'macdsignal': {}}}, - 'volume': {'showBuySell': 'true', 'showVolumeProfile': 'true', 'VolumeProfileHistoryBars': 9, 'VolumeProfilePriceRangeSplices': 111}}, + 'volume': {'showBuySell': 'true', 'showVolumeProfile': 'true', + 'VolumeProfileHistoryBars': 9, 'VolumeProfilePriceRangeSplices': 111}}, ) ]) From e7ead1ec97588dd59116cdda041c9c451cd44321 Mon Sep 17 00:00:00 2001 From: Felix Meier Date: Tue, 26 Oct 2021 08:10:22 +0200 Subject: [PATCH 7/7] separating add_volumeProfile method --- freqtrade/plot/plotting.py | 72 +++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 380c1535c..ec7eb42a1 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -424,6 +424,45 @@ def createVolumeProfileData(data: pd.DataFrame, bar_split: int = 50, return df_vol +def add_volumeProfile(data: pd.DataFrame, fig, plot_config: Dict[str, Dict]): + # load VolumeProfile configurations + volume_config = plot_config['volume'] if 'volume' in plot_config else {} + showVolumeProfile = volume_config['showVolumeProfile'] if 'showVolumeProfile' in \ + volume_config else 'false' + VolumeProfileHistoryBars = volume_config['VolumeProfileHistoryBars'] if \ + 'VolumeProfileHistoryBars' in volume_config else -1 + VolumeProfilePriceRangeSplices = volume_config[ + 'VolumeProfilePriceRangeSplices'] if 'VolumeProfilePriceRangeSplices' in \ + volume_config else 50 + + # Show VolumeProfile + if showVolumeProfile.lower() == 'true': + + volumeProfileData = createVolumeProfileData( + data, VolumeProfilePriceRangeSplices, VolumeProfileHistoryBars) + + volume_buy = go.Bar( + x=volumeProfileData['volume_buy'], + y=volumeProfileData['price_avg'], + name='VolumeProfile Buy', + marker_color='rgba(99, 146, 52, 0.6)', + marker_line_color='rgba(99, 146, 52, 0.6)', + orientation='h', + ) + volume_sell = go.Bar( + x=volumeProfileData['volume_sell'], + y=volumeProfileData['price_avg'], + name='VolumeProfile Sell', + marker_color='rgba(208, 41, 41, 0.6)', + marker_line_color='rgba(208, 41, 41, 0.6)', + orientation='h', + ) + + fig.add_trace(volume_sell, 1, 2) + fig.add_trace(volume_buy, 1, 2) + fig.update_layout(barmode='stack') + + def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFrame = None, *, indicators1: List[str] = [], indicators2: List[str] = [], @@ -449,11 +488,6 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra showBuySell = volume_config['showBuySell'] if 'showBuySell' in volume_config else 'false' showVolumeProfile = volume_config['showVolumeProfile'] if 'showVolumeProfile' in \ volume_config else 'false' - VolumeProfileHistoryBars = volume_config['VolumeProfileHistoryBars'] if \ - 'VolumeProfileHistoryBars' in volume_config else -1 - VolumeProfilePriceRangeSplices = volume_config[ - 'VolumeProfilePriceRangeSplices'] if 'VolumeProfilePriceRangeSplices' in \ - volume_config else 50 # Define the graph fig = make_subplots( @@ -537,33 +571,7 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra fig = add_areas(fig, 1, data, plot_config['main_plot']) fig = plot_trades(fig, trades) - # VolumeProfile - - if showVolumeProfile.lower() == 'true': - - volumeProfileData = createVolumeProfileData( - data, VolumeProfilePriceRangeSplices, VolumeProfileHistoryBars) - - volume_buy = go.Bar( - x=volumeProfileData['volume_buy'], - y=volumeProfileData['price_avg'], - name='VolumeProfile Buy', - marker_color='rgba(99, 146, 52, 0.6)', - marker_line_color='rgba(99, 146, 52, 0.6)', - orientation='h', - ) - volume_sell = go.Bar( - x=volumeProfileData['volume_sell'], - y=volumeProfileData['price_avg'], - name='VolumeProfile Sell', - marker_color='rgba(208, 41, 41, 0.6)', - marker_line_color='rgba(208, 41, 41, 0.6)', - orientation='h', - ) - - fig.add_trace(volume_sell, 1, 2) - fig.add_trace(volume_buy, 1, 2) - fig.update_layout(barmode='stack') + add_volumeProfile(data, fig, plot_config) # standard volume plot if showBuySell.lower() == 'true': # show volume plot split by sell & buy trades