Merge branch 'develop' into bt_add_maxdrawdown
This commit is contained in:
		| @@ -18,6 +18,7 @@ from werkzeug.serving import make_server | ||||
| from freqtrade.__init__ import __version__ | ||||
| from freqtrade.constants import DATETIME_PRINT_FORMAT | ||||
| from freqtrade.rpc.rpc import RPC, RPCException | ||||
| from freqtrade.rpc.fiat_convert import CryptoToFiatConverter | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
| @@ -56,7 +57,7 @@ def require_login(func: Callable[[Any, Any], Any]): | ||||
|  | ||||
|  | ||||
| # Type should really be Callable[[ApiServer], Any], but that will create a circular dependency | ||||
| def rpc_catch_errors(func: Callable[[Any], Any]): | ||||
| def rpc_catch_errors(func: Callable[..., Any]): | ||||
|  | ||||
|     def func_wrapper(obj, *args, **kwargs): | ||||
|  | ||||
| @@ -106,6 +107,9 @@ class ApiServer(RPC): | ||||
|         # Register application handling | ||||
|         self.register_rest_rpc_urls() | ||||
|  | ||||
|         if self._config.get('fiat_display_currency', None): | ||||
|             self._fiat_converter = CryptoToFiatConverter() | ||||
|  | ||||
|         thread = threading.Thread(target=self.run, daemon=True) | ||||
|         thread.start() | ||||
|  | ||||
| @@ -197,6 +201,8 @@ class ApiServer(RPC): | ||||
|                               view_func=self._ping, methods=['GET']) | ||||
|         self.app.add_url_rule(f'{BASE_URI}/trades', 'trades', | ||||
|                               view_func=self._trades, methods=['GET']) | ||||
|         self.app.add_url_rule(f'{BASE_URI}/trades/<int:tradeid>', 'trades_delete', | ||||
|                               view_func=self._trades_delete, methods=['DELETE']) | ||||
|         # Combined actions and infos | ||||
|         self.app.add_url_rule(f'{BASE_URI}/blacklist', 'blacklist', view_func=self._blacklist, | ||||
|                               methods=['GET', 'POST']) | ||||
| @@ -421,6 +427,19 @@ class ApiServer(RPC): | ||||
|         results = self._rpc_trade_history(limit) | ||||
|         return self.rest_dump(results) | ||||
|  | ||||
|     @require_login | ||||
|     @rpc_catch_errors | ||||
|     def _trades_delete(self, tradeid): | ||||
|         """ | ||||
|         Handler for DELETE /trades/<tradeid> endpoint. | ||||
|         Removes the trade from the database (tries to cancel open orders first!) | ||||
|         get: | ||||
|           param: | ||||
|             tradeid: Numeric trade-id assigned to the trade. | ||||
|         """ | ||||
|         result = self._rpc_delete(tradeid) | ||||
|         return self.rest_dump(result) | ||||
|  | ||||
|     @require_login | ||||
|     @rpc_catch_errors | ||||
|     def _whitelist(self): | ||||
|   | ||||
| @@ -6,12 +6,14 @@ from abc import abstractmethod | ||||
| from datetime import date, datetime, timedelta | ||||
| from enum import Enum | ||||
| from math import isnan | ||||
| from typing import Any, Dict, List, Optional, Tuple | ||||
| from typing import Any, Dict, List, Optional, Tuple, Union | ||||
|  | ||||
| import arrow | ||||
| from numpy import NAN, mean | ||||
|  | ||||
| from freqtrade.exceptions import ExchangeError, PricingError | ||||
| from freqtrade.exceptions import (ExchangeError, InvalidOrderException, | ||||
|                                   PricingError) | ||||
| from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs | ||||
| from freqtrade.misc import shorten_date | ||||
| from freqtrade.persistence import Trade | ||||
| from freqtrade.rpc.fiat_convert import CryptoToFiatConverter | ||||
| @@ -103,6 +105,8 @@ class RPC: | ||||
|             'trailing_only_offset_is_reached': config.get('trailing_only_offset_is_reached'), | ||||
|             'ticker_interval': config['timeframe'],  # DEPRECATED | ||||
|             'timeframe': config['timeframe'], | ||||
|             'timeframe_ms': timeframe_to_msecs(config['timeframe']), | ||||
|             'timeframe_min': timeframe_to_minutes(config['timeframe']), | ||||
|             'exchange': config['exchange']['name'], | ||||
|             'strategy': config['strategy'], | ||||
|             'forcebuy_enabled': config.get('forcebuy_enable', False), | ||||
| @@ -248,9 +252,10 @@ class RPC: | ||||
|     def _rpc_trade_history(self, limit: int) -> Dict: | ||||
|         """ Returns the X last trades """ | ||||
|         if limit > 0: | ||||
|             trades = Trade.get_trades().order_by(Trade.id.desc()).limit(limit) | ||||
|             trades = Trade.get_trades([Trade.is_open.is_(False)]).order_by( | ||||
|                 Trade.id.desc()).limit(limit) | ||||
|         else: | ||||
|             trades = Trade.get_trades().order_by(Trade.id.desc()).all() | ||||
|             trades = Trade.get_trades([Trade.is_open.is_(False)]).order_by(Trade.id.desc()).all() | ||||
|  | ||||
|         output = [trade.to_json() for trade in trades] | ||||
|  | ||||
| @@ -519,7 +524,7 @@ class RPC: | ||||
|         # check if valid pair | ||||
|  | ||||
|         # check if pair already has an open pair | ||||
|         trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair.is_(pair)]).first() | ||||
|         trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first() | ||||
|         if trade: | ||||
|             raise RPCException(f'position for {pair} already open - id: {trade.id}') | ||||
|  | ||||
| @@ -528,11 +533,51 @@ class RPC: | ||||
|  | ||||
|         # execute buy | ||||
|         if self._freqtrade.execute_buy(pair, stakeamount, price): | ||||
|             trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair.is_(pair)]).first() | ||||
|             trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first() | ||||
|             return trade | ||||
|         else: | ||||
|             return None | ||||
|  | ||||
|     def _rpc_delete(self, trade_id: str) -> Dict[str, Union[str, int]]: | ||||
|         """ | ||||
|         Handler for delete <id>. | ||||
|         Delete the given trade and close eventually existing open orders. | ||||
|         """ | ||||
|         with self._freqtrade._sell_lock: | ||||
|             c_count = 0 | ||||
|             trade = Trade.get_trades(trade_filter=[Trade.id == trade_id]).first() | ||||
|             if not trade: | ||||
|                 logger.warning('delete trade: Invalid argument received') | ||||
|                 raise RPCException('invalid argument') | ||||
|  | ||||
|             # Try cancelling regular order if that exists | ||||
|             if trade.open_order_id: | ||||
|                 try: | ||||
|                     self._freqtrade.exchange.cancel_order(trade.open_order_id, trade.pair) | ||||
|                     c_count += 1 | ||||
|                 except (ExchangeError, InvalidOrderException): | ||||
|                     pass | ||||
|  | ||||
|             # cancel stoploss on exchange ... | ||||
|             if (self._freqtrade.strategy.order_types.get('stoploss_on_exchange') | ||||
|                     and trade.stoploss_order_id): | ||||
|                 try: | ||||
|                     self._freqtrade.exchange.cancel_stoploss_order(trade.stoploss_order_id, | ||||
|                                                                    trade.pair) | ||||
|                     c_count += 1 | ||||
|                 except (ExchangeError, InvalidOrderException): | ||||
|                     pass | ||||
|  | ||||
|             Trade.session.delete(trade) | ||||
|             Trade.session.flush() | ||||
|             self._freqtrade.wallets.update() | ||||
|             return { | ||||
|                 'result': 'success', | ||||
|                 'trade_id': trade_id, | ||||
|                 'result_msg': f'Deleted trade {trade_id}. Closed {c_count} open orders.', | ||||
|                 'cancel_order_count': c_count, | ||||
|             } | ||||
|  | ||||
|     def _rpc_performance(self) -> List[Dict[str, Any]]: | ||||
|         """ | ||||
|         Handler for performance. | ||||
|   | ||||
| @@ -5,6 +5,7 @@ This module manage Telegram communication | ||||
| """ | ||||
| import json | ||||
| import logging | ||||
| import arrow | ||||
| from typing import Any, Callable, Dict | ||||
|  | ||||
| from tabulate import tabulate | ||||
| @@ -92,6 +93,8 @@ class Telegram(RPC): | ||||
|             CommandHandler('stop', self._stop), | ||||
|             CommandHandler('forcesell', self._forcesell), | ||||
|             CommandHandler('forcebuy', self._forcebuy), | ||||
|             CommandHandler('trades', self._trades), | ||||
|             CommandHandler('delete', self._delete_trade), | ||||
|             CommandHandler('performance', self._performance), | ||||
|             CommandHandler('daily', self._daily), | ||||
|             CommandHandler('count', self._count), | ||||
| @@ -496,6 +499,62 @@ class Telegram(RPC): | ||||
|         except RPCException as e: | ||||
|             self._send_msg(str(e)) | ||||
|  | ||||
|     @authorized_only | ||||
|     def _trades(self, update: Update, context: CallbackContext) -> None: | ||||
|         """ | ||||
|         Handler for /trades <n> | ||||
|         Returns last n recent trades. | ||||
|         :param bot: telegram bot | ||||
|         :param update: message update | ||||
|         :return: None | ||||
|         """ | ||||
|         stake_cur = self._config['stake_currency'] | ||||
|         try: | ||||
|             nrecent = int(context.args[0]) | ||||
|         except (TypeError, ValueError, IndexError): | ||||
|             nrecent = 10 | ||||
|         try: | ||||
|             trades = self._rpc_trade_history( | ||||
|                 nrecent | ||||
|             ) | ||||
|             trades_tab = tabulate( | ||||
|                 [[arrow.get(trade['open_date']).humanize(), | ||||
|                   trade['pair'], | ||||
|                   f"{(100 * trade['close_profit']):.2f}% ({trade['close_profit_abs']})"] | ||||
|                  for trade in trades['trades']], | ||||
|                 headers=[ | ||||
|                     'Open Date', | ||||
|                     'Pair', | ||||
|                     f'Profit ({stake_cur})', | ||||
|                 ], | ||||
|                 tablefmt='simple') | ||||
|             message = (f"<b>{min(trades['trades_count'], nrecent)} recent trades</b>:\n" | ||||
|                        + (f"<pre>{trades_tab}</pre>" if trades['trades_count'] > 0 else '')) | ||||
|             self._send_msg(message, parse_mode=ParseMode.HTML) | ||||
|         except RPCException as e: | ||||
|             self._send_msg(str(e)) | ||||
|  | ||||
|     @authorized_only | ||||
|     def _delete_trade(self, update: Update, context: CallbackContext) -> None: | ||||
|         """ | ||||
|         Handler for /delete <id>. | ||||
|         Delete the given trade | ||||
|         :param bot: telegram bot | ||||
|         :param update: message update | ||||
|         :return: None | ||||
|         """ | ||||
|  | ||||
|         trade_id = context.args[0] if len(context.args) > 0 else None | ||||
|         try: | ||||
|             msg = self._rpc_delete(trade_id) | ||||
|             self._send_msg(( | ||||
|                 '`{result_msg}`\n' | ||||
|                 'Please make sure to take care of this asset on the exchange manually.' | ||||
|             ).format(**msg)) | ||||
|  | ||||
|         except RPCException as e: | ||||
|             self._send_msg(str(e)) | ||||
|  | ||||
|     @authorized_only | ||||
|     def _performance(self, update: Update, context: CallbackContext) -> None: | ||||
|         """ | ||||
| @@ -609,10 +668,12 @@ class Telegram(RPC): | ||||
|                    "         *table :* `will display trades in a table`\n" | ||||
|                    "                `pending buy orders are marked with an asterisk (*)`\n" | ||||
|                    "                `pending sell orders are marked with a double asterisk (**)`\n" | ||||
|                    "*/trades [limit]:* `Lists last closed trades (limited to 10 by default)`\n" | ||||
|                    "*/profit:* `Lists cumulative profit from all finished trades`\n" | ||||
|                    "*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, " | ||||
|                    "regardless of profit`\n" | ||||
|                    f"{forcebuy_text if self._config.get('forcebuy_enable', False) else ''}" | ||||
|                    "*/delete <trade_id>:* `Instantly delete the given trade in the database`\n" | ||||
|                    "*/performance:* `Show performance of each finished trade grouped by pair`\n" | ||||
|                    "*/daily <n>:* `Shows profit or loss per day, over the last n days`\n" | ||||
|                    "*/count:* `Show number of trades running compared to allowed number of trades`" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user