diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index 357bcb937..b5611c8aa 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -260,6 +260,77 @@ class Arguments(object): default=None ) + + self.parser.add_argument( + '--stop-loss', + help='Renders stop/loss information in the main chart', + dest='stoplossdisplay', + action='store_true', + default=False + + ) + + self.parser.add_argument( + '--plot-rsi', + help='Renders a rsi chart of the given RSI dataframe name, for example --plot-rsi rsi', + dest='plotrsi', + nargs='+', + default=None + + ) + + self.parser.add_argument( + '--plot-cci', + help='Renders a cci chart of the given CCI dataframe name, for example --plot-cci cci', + dest='plotcci', + nargs='+', + + default=None + ) + + self.parser.add_argument( + '--plot-macd', + help='Renders a macd chart of the given ' + 'dataframe name, for example --plot-macd macd', + dest='plotmacd', + default=None + ) + + self.parser.add_argument( + '--plot-dataframe', + help='Renders the specified dataframes', + dest='plotdataframe', + default=None, + nargs='+', + type=str + ) + + self.parser.add_argument( + '--plot-dataframe-marker', + help='Renders the specified dataframes as markers. ' + 'Accepted values for a marker are either 100 or -100', + dest='plotdataframemarker', + default=None, + nargs='+', + type=str + ) + + self.parser.add_argument( + '--plot-volume', + help='plots the volume as a sub plot', + dest='plotvolume', + action='store_true' + ) + + self.parser.add_argument( + '--plot-max-ticks', + help='specify an upper limit of how many ticks we can display', + dest='plotticks', + default=750, + type=int + ) + + def testdata_dl_options(self) -> None: """ Parses given arguments for testdata download diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 81749d778..086c408ac 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -357,8 +357,9 @@ class Telegram(RPC): message = tabulate({ 'current': [len(trades)], - 'max': [self._config['max_open_trades']] - }, headers=['current', 'max'], tablefmt='simple') + 'max': [self._config['max_open_trades']], + 'total stake': [sum((trade.open_rate * trade.amount) for trade in trades)] + }, headers=['current', 'max', 'total stake'], tablefmt='simple') message = "
{}
".format(message) logger.debug(message) self.send_msg(message, parse_mode=ParseMode.HTML) diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index b534dfeb5..38e9c8b8a 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -1013,9 +1013,12 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: msg_mock.reset_mock() telegram._count(bot=MagicMock(), update=update) - msg = '
  current    max\n---------  -----\n        1      {}
'.format( - default_conf['max_open_trades'] - ) + msg = '
  current    max    total stake\n---------  -----  -------------\n' \
+          '        1      {}          {}
'\ + .format( + default_conf['max_open_trades'], + default_conf['stake_amount'] + ) assert msg in msg_mock.call_args_list[0][0][0] diff --git a/requirements.txt b/requirements.txt index 4aa22fcd1..de0fd2181 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ ccxt==1.11.149 SQLAlchemy==1.2.7 -python-telegram-bot==10.0.2 +python-telegram-bot==10.1.0 arrow==0.12.1 cachetools==2.0.1 requests==2.18.4 diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index 5e533a030..68e17208c 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -11,21 +11,21 @@ Optional Cli parameters --timerange: specify what timerange of data to use. -l / --live: Live, to download the latest ticker for the pair """ +import datetime import logging import sys from argparse import Namespace - from typing import List +import plotly.graph_objs as go from plotly import tools from plotly.offline import plot -import plotly.graph_objs as go -from freqtrade.arguments import Arguments -from freqtrade.analyze import Analyze -from freqtrade import exchange import freqtrade.optimize as optimize - +from freqtrade import exchange +from freqtrade.analyze import Analyze +from freqtrade.arguments import Arguments +from freqtrade.configuration import Configuration <<<<<<< HEAD logger = logging.getLogger('freqtrade') @@ -33,6 +33,203 @@ logger = logging.getLogger('freqtrade') logger = logging.getLogger(__name__) >>>>>>> bddf009a2b6d0e1a19cca558887ce972e99a6238 +def plot_dataframes_markers(data, fig, args): + """ + plots additional dataframe markers in the main plot + :param data: + :param fig: + :param args: + :return: + """ + + if args.plotdataframemarker: + for x in args.plotdataframemarker: + filter = data[(data[x] == 100 ) | (data[x] == -100) ] + marker = go.Scatter( + x=filter.date, + y=filter.low * 0.99, + mode='markers', + name=x, + marker=dict( + symbol='diamond-tall-open', + size=10, + line=dict(width=1) + ) + + ) + + fig.append_trace(marker, 1, 1) + + +def plot_dataframes(data, fig, args): + """ + plots additional dataframes in the main plot + :param data: + :param fig: + :param args: + :return: + """ + + if args.plotdataframe: + for x in args.plotdataframe: + chart = go.Scattergl(x=data['date'], y=data[x], name=x) + fig.append_trace(chart, 1, 1) + + +def plot_volume_dataframe(data, fig, args, plotnumber): + """ + adds the plotting of the volume + :param data: + :param fig: + :param args: + :return: + """ + + volume = go.Bar(x=data['date'], y=data['volume'], name='Volume') + fig.append_trace(volume, plotnumber, 1) + + +def plot_macd_dataframe(data, fig, args, plotnumber): + """ + adds the plotting of the MACD if specified + :param data: + :param fig: + :param args: + :return: + """ + + macd = go.Scattergl(x=data['date'], y=data[args.plotmacd], name='MACD') + macdsignal = go.Scattergl(x=data['date'], y=data[args.plotmacd + 'signal'], name='MACD signal') + fig.append_trace(macd, plotnumber, 1) + fig.append_trace(macdsignal, plotnumber, 1) + + +def plot_rsi_dataframe(data, fig, args, plotnumber): + """ + + this function plots an additional RSI chart under the exiting charts + :param data: + :param fig: + :param args: + :return: + """ + if args.plotrsi: + for x in args.plotrsi: + rsi = go.Scattergl(x=data['date'], y=data[x], name=x) + fig.append_trace(rsi, plotnumber, 1) + + +def plot_cci_dataframe(data, fig, args, plotnumber): + """ + + this function plots an additional cci chart under the exiting charts + :param data: + :param fig: + :param args: + :return: + """ + if args.plotcci: + for x in args.plotcci: + chart = go.Scattergl(x=data['date'], y=data[x], name=x) + fig.append_trace(chart, plotnumber, 1) + + + +def plot_stop_loss_trade(df_sell, fig, analyze, args): + """ + plots the stop loss for the associated trades and buys + as well as the estimated profit ranges. + + will be enabled if --stop-loss is provided + as argument + + :param data: + :param trades: + :return: + """ + + if args.stoplossdisplay is False: + return + + if 'associated_buy_price' not in df_sell: + return + + stoploss = analyze.strategy.stoploss + + for index, x in df_sell.iterrows(): + if x['associated_buy_price'] > 0: + # draw stop loss + fig['layout']['shapes'].append( + { + 'fillcolor': 'red', + 'opacity': 0.1, + 'type': 'rect', + 'x0': x['associated_buy_date'], + 'x1': x['date'], + 'y0': x['associated_buy_price'], + 'y1': (x['associated_buy_price'] - abs(stoploss) * x['associated_buy_price']), + 'line': {'color': 'red'} + } + ) + + totalTime = 0 + for time in analyze.strategy.minimal_roi: + t = int(time) + totalTime = t + totalTime + + enddate = x['date'] + + date = x['associated_buy_date'] + datetime.timedelta(minutes=totalTime) + + # draw profit range + fig['layout']['shapes'].append( + { + 'fillcolor': 'green', + 'opacity': 0.1, + 'type': 'rect', + 'x0': date, + 'x1': enddate, + 'y0': x['associated_buy_price'], + 'y1': x['associated_buy_price'] + x['associated_buy_price'] * analyze.strategy.minimal_roi[ + time], + 'line': {'color': 'green'} + } + ) + + +def find_profits(data): + """ + finds the profits between sells and the associated buys. This does not take in account + ROI! + :param data: + :return: + """ + + # go over all the sells + # find all previous buys + + df_sell = data[data['sell'] == 1] + df_sell['profit'] = 0 + df_buys = data[data['buy'] == 1] + lastDate = data['date'].iloc[0] + + for index, row in df_sell.iterrows(): + + buys = df_buys[(df_buys['date'] < row['date']) & (df_buys['date'] > lastDate)] + + profit = None + if buys['date'].count() > 0: + buys = buys.tail() + profit = round(row['close'] / buys['close'].values[0] * 100 - 100, 2) + lastDate = row['date'] + + df_sell.loc[index, 'associated_buy_date'] = buys['date'].values[0] + df_sell.loc[index, 'associated_buy_price'] = buys['close'].values[0] + + df_sell.loc[index, 'profit'] = profit + + return df_sell + def plot_analyzed_dataframe(args: Namespace) -> None: """ @@ -44,7 +241,9 @@ def plot_analyzed_dataframe(args: Namespace) -> None: # Init strategy try: - analyze = Analyze({'strategy': args.strategy}) + config = Configuration(args) + + analyze = Analyze(config.get_config()) except AttributeError: logger.critical( 'Impossible to load the strategy. Please check the file "user_data/strategies/%s.py"', @@ -73,9 +272,9 @@ def plot_analyzed_dataframe(args: Namespace) -> None: dataframe = analyze.populate_buy_trend(dataframe) dataframe = analyze.populate_sell_trend(dataframe) - if len(dataframe.index) > 750: - logger.warning('Ticker contained more than 750 candles, clipping.') - data = dataframe.tail(750) + if len(dataframe.index) > args.plotticks: + logger.warning('Ticker contained more than {} candles, clipping.'.format(args.plotticks)) + data = dataframe.tail(args.plotticks) candles = go.Candlestick( x=data.date, @@ -87,30 +286,35 @@ def plot_analyzed_dataframe(args: Namespace) -> None: ) df_buy = data[data['buy'] == 1] + buys = go.Scattergl( x=df_buy.date, - y=df_buy.close, + y=df_buy.close * 0.995, mode='markers', name='buy', marker=dict( symbol='triangle-up-dot', - size=9, + size=15, line=dict(width=1), color='green', ) ) - df_sell = data[data['sell'] == 1] - sells = go.Scattergl( + df_sell = find_profits(data) + + sells = go.Scatter( x=df_sell.date, - y=df_sell.close, - mode='markers', + y=df_sell.close * 1.01, + mode='markers+text', name='sell', + text=df_sell.profit, + textposition='top right', marker=dict( symbol='triangle-down-dot', - size=9, + size=15, line=dict(width=1), color='red', ) + ) bb_lower = go.Scatter( @@ -127,31 +331,77 @@ def plot_analyzed_dataframe(args: Namespace) -> None: fillcolor="rgba(0,176,246,0.2)", line={'color': "transparent"}, ) - macd = go.Scattergl(x=data['date'], y=data['macd'], name='MACD') - macdsignal = go.Scattergl(x=data['date'], y=data['macdsignal'], name='MACD signal') - volume = go.Bar(x=data['date'], y=data['volume'], name='Volume') + bb_middle = go.Scatter( + x=data.date, + y=data.bb_middleband, + name='BB middle', + fill="tonexty", + fillcolor="rgba(0,176,246,0.2)", + line={'color': "red"}, + ) + # ugly hack for now + rowWidth = [1] + if args.plotvolume: + rowWidth.append(1) + if args.plotmacd: + rowWidth.append(1) + if args.plotrsi: + rowWidth.append(1) + if args.plotcci: + rowWidth.append(1) + + # standard layout signal + volume fig = tools.make_subplots( - rows=3, + rows=len(rowWidth), cols=1, shared_xaxes=True, - row_width=[1, 1, 4], + row_width=rowWidth, vertical_spacing=0.0001, ) + # todo should be optional fig.append_trace(candles, 1, 1) fig.append_trace(bb_lower, 1, 1) + fig.append_trace(bb_middle, 1, 1) fig.append_trace(bb_upper, 1, 1) + fig.append_trace(buys, 1, 1) fig.append_trace(sells, 1, 1) - fig.append_trace(volume, 2, 1) - fig.append_trace(macd, 3, 1) - fig.append_trace(macdsignal, 3, 1) + + # append stop loss/profit + plot_stop_loss_trade(df_sell, fig, analyze, args) + + # plot other dataframes + plot_dataframes(data, fig, args) + plot_dataframes_markers(data, fig, args) fig['layout'].update(title=args.pair) fig['layout']['yaxis1'].update(title='Price') - fig['layout']['yaxis2'].update(title='Volume') - fig['layout']['yaxis3'].update(title='MACD') + + subplots = 1 + + if args.plotvolume: + subplots = subplots + 1 + plot_volume_dataframe(data, fig, args, subplots) + fig['layout']['yaxis' + str(subplots)].update(title='Volume') + + if args.plotmacd: + subplots = subplots + 1 + plot_macd_dataframe(data, fig, args, subplots) + fig['layout']['yaxis' + str(subplots)].update(title='MACD') + + if args.plotrsi: + subplots = subplots + 1 + plot_rsi_dataframe(data, fig, args, subplots) + fig['layout']['yaxis' + str(subplots)].update(title='RSI', range=[0, 100]) + + if args.plotcci: + subplots = subplots + 1 + plot_cci_dataframe(data, fig, args, subplots) + fig['layout']['yaxis' + str(subplots)].update(title='CCI') + + # updated all the plot(fig, filename='freqtrade-plot.html')