From 4df44d8b3256e5d7d7294a8f560716a75efbb2e1 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 1 Mar 2019 01:26:29 +0300 Subject: [PATCH 01/17] wallets cleanup --- freqtrade/constants.py | 2 ++ freqtrade/freqtradebot.py | 2 +- freqtrade/tests/test_wallets.py | 42 ++++++++++++++++----------------- freqtrade/wallets.py | 39 ++++++++++++++++-------------- 4 files changed, 45 insertions(+), 40 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 4d0907d78..6e504913e 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -20,6 +20,7 @@ REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange'] ORDERTYPE_POSSIBILITIES = ['limit', 'market'] ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList'] +DRY_RUN_WALLET = 999.9 TICKER_INTERVAL_MINUTES = { '1m': 1, @@ -60,6 +61,7 @@ CONF_SCHEMA = { }, 'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT}, 'dry_run': {'type': 'boolean'}, + 'dry_run_wallet': {'type': 'number'}, 'process_only_new_candles': {'type': 'boolean'}, 'minimal_roi': { 'type': 'object', diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index c51dd4673..9671c4fa9 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -58,7 +58,7 @@ class FreqtradeBot(object): exchange_name = self.config.get('exchange', {}).get('name', 'bittrex').title() self.exchange = ExchangeResolver(exchange_name, self.config).exchange - self.wallets = Wallets(self.exchange) + self.wallets = Wallets(self.config, self.exchange) self.dataprovider = DataProvider(self.config, self.exchange) # Attach Dataprovider to Strategy baseclass diff --git a/freqtrade/tests/test_wallets.py b/freqtrade/tests/test_wallets.py index 8d9adc74c..2c493cfc3 100644 --- a/freqtrade/tests/test_wallets.py +++ b/freqtrade/tests/test_wallets.py @@ -23,13 +23,13 @@ def test_sync_wallet_at_boot(mocker, default_conf): freqtrade = get_patched_freqtradebot(mocker, default_conf) - assert len(freqtrade.wallets.wallets) == 2 - assert freqtrade.wallets.wallets['BNT'].free == 1.0 - assert freqtrade.wallets.wallets['BNT'].used == 2.0 - assert freqtrade.wallets.wallets['BNT'].total == 3.0 - assert freqtrade.wallets.wallets['GAS'].free == 0.260739 - assert freqtrade.wallets.wallets['GAS'].used == 0.0 - assert freqtrade.wallets.wallets['GAS'].total == 0.260739 + assert len(freqtrade.wallets._wallets) == 2 + assert freqtrade.wallets._wallets['BNT'].free == 1.0 + assert freqtrade.wallets._wallets['BNT'].used == 2.0 + assert freqtrade.wallets._wallets['BNT'].total == 3.0 + assert freqtrade.wallets._wallets['GAS'].free == 0.260739 + assert freqtrade.wallets._wallets['GAS'].used == 0.0 + assert freqtrade.wallets._wallets['GAS'].total == 0.260739 assert freqtrade.wallets.get_free('BNT') == 1.0 mocker.patch.multiple( @@ -50,13 +50,13 @@ def test_sync_wallet_at_boot(mocker, default_conf): freqtrade.wallets.update() - assert len(freqtrade.wallets.wallets) == 2 - assert freqtrade.wallets.wallets['BNT'].free == 1.2 - assert freqtrade.wallets.wallets['BNT'].used == 1.9 - assert freqtrade.wallets.wallets['BNT'].total == 3.5 - assert freqtrade.wallets.wallets['GAS'].free == 0.270739 - assert freqtrade.wallets.wallets['GAS'].used == 0.1 - assert freqtrade.wallets.wallets['GAS'].total == 0.260439 + assert len(freqtrade.wallets._wallets) == 2 + assert freqtrade.wallets._wallets['BNT'].free == 1.2 + assert freqtrade.wallets._wallets['BNT'].used == 1.9 + assert freqtrade.wallets._wallets['BNT'].total == 3.5 + assert freqtrade.wallets._wallets['GAS'].free == 0.270739 + assert freqtrade.wallets._wallets['GAS'].used == 0.1 + assert freqtrade.wallets._wallets['GAS'].total == 0.260439 assert freqtrade.wallets.get_free('GAS') == 0.270739 assert freqtrade.wallets.get_used('GAS') == 0.1 assert freqtrade.wallets.get_total('GAS') == 0.260439 @@ -81,11 +81,11 @@ def test_sync_wallet_missing_data(mocker, default_conf): freqtrade = get_patched_freqtradebot(mocker, default_conf) - assert len(freqtrade.wallets.wallets) == 2 - assert freqtrade.wallets.wallets['BNT'].free == 1.0 - assert freqtrade.wallets.wallets['BNT'].used == 2.0 - assert freqtrade.wallets.wallets['BNT'].total == 3.0 - assert freqtrade.wallets.wallets['GAS'].free == 0.260739 - assert freqtrade.wallets.wallets['GAS'].used is None - assert freqtrade.wallets.wallets['GAS'].total == 0.260739 + assert len(freqtrade.wallets._wallets) == 2 + assert freqtrade.wallets._wallets['BNT'].free == 1.0 + assert freqtrade.wallets._wallets['BNT'].used == 2.0 + assert freqtrade.wallets._wallets['BNT'].total == 3.0 + assert freqtrade.wallets._wallets['GAS'].free == 0.260739 + assert freqtrade.wallets._wallets['GAS'].used is None + assert freqtrade.wallets._wallets['GAS'].total == 0.260739 assert freqtrade.wallets.get_free('GAS') == 0.260739 diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 1f1d2c511..c8ab90276 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -1,15 +1,16 @@ # pragma pylint: disable=W0603 """ Wallet """ + import logging -from typing import Dict, Any, NamedTuple +from typing import Dict, NamedTuple from freqtrade.exchange import Exchange +from freqtrade import constants logger = logging.getLogger(__name__) # wallet data structure class Wallet(NamedTuple): - exchange: str currency: str free: float = 0 used: float = 0 @@ -18,17 +19,19 @@ class Wallet(NamedTuple): class Wallets(object): - def __init__(self, exchange: Exchange) -> None: - self.exchange = exchange - self.wallets: Dict[str, Any] = {} + def __init__(self, config: dict, exchange: Exchange) -> None: + self._config = config + self._exchange = exchange + self._wallets: Dict[str, Wallet] = {} + self.update() def get_free(self, currency) -> float: - if self.exchange._conf['dry_run']: - return 999.9 + if self._config['dry_run']: + return self._config.get('dry_run_wallet', constants.DRY_RUN_WALLET) - balance = self.wallets.get(currency) + balance = self._wallets.get(currency) if balance and balance.free: return balance.free else: @@ -36,10 +39,10 @@ class Wallets(object): def get_used(self, currency) -> float: - if self.exchange._conf['dry_run']: - return 999.9 + if self._config['dry_run']: + return self._config.get('dry_run_wallet', constants.DRY_RUN_WALLET) - balance = self.wallets.get(currency) + balance = self._wallets.get(currency) if balance and balance.used: return balance.used else: @@ -47,25 +50,25 @@ class Wallets(object): def get_total(self, currency) -> float: - if self.exchange._conf['dry_run']: - return 999.9 + if self._config['dry_run']: + return self._config.get('dry_run_wallet', constants.DRY_RUN_WALLET) - balance = self.wallets.get(currency) + balance = self._wallets.get(currency) if balance and balance.total: return balance.total else: return 0 def update(self) -> None: - balances = self.exchange.get_balances() + + balances = self._exchange.get_balances() for currency in balances: - self.wallets[currency] = Wallet( - self.exchange.id, + self._wallets[currency] = Wallet( currency, balances[currency].get('free', None), balances[currency].get('used', None), balances[currency].get('total', None) ) - logger.info('Wallets synced ...') + logger.info('Wallets synced.') From b792f00553362a0e5ba17be7577e08e3d0f3b360 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 1 Mar 2019 02:13:16 +0300 Subject: [PATCH 02/17] exchange cleanup --- freqtrade/exchange/exchange.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 874ed93aa..474595787 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -66,7 +66,7 @@ def retrier(f): class Exchange(object): - _conf: Dict = {} + _config: Dict = {} _params: Dict = {} # Dict to specify which options each exchange implements @@ -82,7 +82,7 @@ class Exchange(object): it does basic validation whether the specified exchange and pairs are valid. :return: None """ - self._conf.update(config) + self._config.update(config) self._cached_ticker: Dict[str, Any] = {} @@ -212,7 +212,7 @@ class Exchange(object): logger.warning('Unable to validate pairs (assuming they are correct).') # return - stake_cur = self._conf['stake_currency'] + stake_cur = self._config['stake_currency'] for pair in pairs: # Note: ccxt has BaseCurrency/QuoteCurrency format for pairs # TODO: add a support for having coins in BTC/USDT format @@ -347,7 +347,7 @@ class Exchange(object): def buy(self, pair: str, ordertype: str, amount: float, rate: float, time_in_force) -> Dict: - if self._conf['dry_run']: + if self._config['dry_run']: dry_order = self.dry_run_order(pair, ordertype, "buy", amount, rate) return dry_order @@ -360,7 +360,7 @@ class Exchange(object): def sell(self, pair: str, ordertype: str, amount: float, rate: float, time_in_force='gtc') -> Dict: - if self._conf['dry_run']: + if self._config['dry_run']: dry_order = self.dry_run_order(pair, ordertype, "sell", amount, rate) return dry_order @@ -385,7 +385,7 @@ class Exchange(object): raise OperationalException( 'In stoploss limit order, stop price should be more than limit price') - if self._conf['dry_run']: + if self._config['dry_run']: dry_order = self.dry_run_order( pair, ordertype, "sell", amount, stop_price) return dry_order @@ -400,8 +400,8 @@ class Exchange(object): @retrier def get_balance(self, currency: str) -> float: - if self._conf['dry_run']: - return 999.9 + if self._config['dry_run']: + return constants.DRY_RUN_WALLET # ccxt exception is already handled by get_balances balances = self.get_balances() @@ -413,7 +413,7 @@ class Exchange(object): @retrier def get_balances(self) -> dict: - if self._conf['dry_run']: + if self._config['dry_run']: return {} try: @@ -584,7 +584,7 @@ class Exchange(object): @retrier def cancel_order(self, order_id: str, pair: str) -> None: - if self._conf['dry_run']: + if self._config['dry_run']: return try: @@ -600,7 +600,7 @@ class Exchange(object): @retrier def get_order(self, order_id: str, pair: str) -> Dict: - if self._conf['dry_run']: + if self._config['dry_run']: order = self._dry_run_open_orders[order_id] return order try: @@ -637,7 +637,7 @@ class Exchange(object): @retrier def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> List: - if self._conf['dry_run']: + if self._config['dry_run']: return [] if not self.exchange_has('fetchMyTrades'): return [] From 6bfc37309e6c14d03a747616883444736f8bf1a4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 13:24:10 +0100 Subject: [PATCH 03/17] refactor getting sell/current rate for telegram and selling fix #1658 --- freqtrade/freqtradebot.py | 24 ++++++++++++++++++++++-- freqtrade/rpc/rpc.py | 12 ++++++------ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 8b9594047..6ba59bea4 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -579,6 +579,25 @@ class FreqtradeBot(object): f"(from {order_amount} to {real_amount}) from Trades") return real_amount + def get_sell_rate(self, pair: str, refresh: bool) -> float: + """ + Get sell rate - either using get-ticker bid or first bid based on orderbook + The orderbook portion is only used for rpc messaging, which would otherwise fail + for BitMex (has no bid/ask in get_ticker) + or remain static in any other case since it's not updating. + :return: Bid rate + """ + config_ask_strategy = self.config.get('ask_strategy', {}) + if config_ask_strategy.get('use_order_book', False): + logger.debug('Using order book to get sell rate') + + order_book = self.exchange.get_order_book(pair, 1) + rate = order_book['asks'][0][0] + + else: + rate = self.exchange.get_ticker(pair, refresh)['bid'] + return rate + def handle_trade(self, trade: Trade) -> bool: """ Sells the current pair if the threshold is reached and updates the trade record. @@ -615,7 +634,7 @@ class FreqtradeBot(object): else: logger.debug('checking sell') - sell_rate = self.exchange.get_ticker(trade.pair)['bid'] + sell_rate = self.get_sell_rate(trade.pair, True) if self.check_sell(trade, sell_rate, buy, sell): return True @@ -858,7 +877,8 @@ class FreqtradeBot(object): """ profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested profit_trade = trade.calc_profit(rate=profit_rate) - current_rate = self.exchange.get_ticker(trade.pair)['bid'] + # Use cached ticker here - it was updated seconds ago. + current_rate = self.get_sell_rate(trade.pair, False) profit_percent = trade.calc_profit_percent(profit_rate) gain = "profit" if profit_percent > 0 else "loss" diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index af64c3d67..ffbc8d334 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -94,7 +94,7 @@ class RPC(object): order = self._freqtrade.exchange.get_order(trade.open_order_id, trade.pair) # calculate profit and send message to user try: - current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] + current_rate = self._freqtrade.get_sell_rate(trade.pair, False) except DependencyException: current_rate = NAN current_profit = trade.calc_profit_percent(current_rate) @@ -125,7 +125,7 @@ class RPC(object): for trade in trades: # calculate profit and send message to user try: - current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] + current_rate = self._freqtrade.get_sell_rate(trade.pair, False) except DependencyException: current_rate = NAN trade_perc = (100 * trade.calc_profit_percent(current_rate)) @@ -213,7 +213,7 @@ class RPC(object): else: # Get current rate try: - current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] + current_rate = self._freqtrade.get_sell_rate(trade.pair, False) except DependencyException: current_rate = NAN profit_percent = trade.calc_profit_percent(rate=current_rate) @@ -280,9 +280,9 @@ class RPC(object): else: try: if coin == 'USDT': - rate = 1.0 / self._freqtrade.exchange.get_ticker('BTC/USDT', False)['bid'] + rate = 1.0 / self._freqtrade.get_sell_rate('BTC/USDT', False) else: - rate = self._freqtrade.exchange.get_ticker(coin + '/BTC', False)['bid'] + rate = self._freqtrade.get_sell_rate(coin + '/BTC', False) except (TemporaryError, DependencyException): continue est_btc: float = rate * balance['total'] @@ -356,7 +356,7 @@ class RPC(object): return # Get current rate and execute sell - current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] + current_rate = self._freqtrade.get_sell_rate(trade.pair, False) self._freqtrade.execute_sell(trade, current_rate, SellType.FORCE_SELL) # ---- EOF def _exec_forcesell ---- From 29aa1598273b7e7ea64abbbba78fc02cb8b95c43 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 13:32:26 +0100 Subject: [PATCH 04/17] Add test for get_sell_rate --- freqtrade/tests/test_freqtradebot.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index fc7c48663..950662bfb 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -2962,6 +2962,31 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order, limit_sell_order assert freqtrade.handle_trade(trade) is True +def test_get_sell_rate(default_conf, mocker, ticker, order_book_l2) -> None: + + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_order_book=order_book_l2, + get_ticker=ticker, + ) + pair = "ETH/BTC" + + # Test regular mode + ft = get_patched_freqtradebot(mocker, default_conf) + rate = ft.get_sell_rate(pair, True) + assert isinstance(rate, float) + assert rate == 0.00001098 + + # Test orderbook mode + default_conf['ask_strategy']['use_order_book'] = True + default_conf['ask_strategy']['order_book_min'] = 1 + default_conf['ask_strategy']['order_book_max'] = 2 + ft = get_patched_freqtradebot(mocker, default_conf) + rate = ft.get_sell_rate(pair, True) + assert isinstance(rate, float) + assert rate == 0.043949 + + def test_startup_messages(default_conf, mocker): default_conf['pairlist'] = {'method': 'VolumePairList', 'config': {'number_assets': 20} From a0e6cd93b62595b71a2edcc8c2b6ebfd7df5229f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 11:27:01 +0100 Subject: [PATCH 05/17] Use bids, not asks for sell-rate detection --- freqtrade/freqtradebot.py | 2 +- freqtrade/tests/test_freqtradebot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 6ba59bea4..cfff46fea 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -592,7 +592,7 @@ class FreqtradeBot(object): logger.debug('Using order book to get sell rate') order_book = self.exchange.get_order_book(pair, 1) - rate = order_book['asks'][0][0] + rate = order_book['bids'][0][0] else: rate = self.exchange.get_ticker(pair, refresh)['bid'] diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 950662bfb..e4f0415f7 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -2984,7 +2984,7 @@ def test_get_sell_rate(default_conf, mocker, ticker, order_book_l2) -> None: ft = get_patched_freqtradebot(mocker, default_conf) rate = ft.get_sell_rate(pair, True) assert isinstance(rate, float) - assert rate == 0.043949 + assert rate == 0.043936 def test_startup_messages(default_conf, mocker): From 2bf7f2feae53666ab3b7fdca0109d991bc11d458 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 16:14:49 +0100 Subject: [PATCH 06/17] Remove duplicate backtest-result-analysi documentation --- docs/backtesting.md | 34 ++++------------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index e155dce88..932783160 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -65,35 +65,7 @@ Where `-s TestStrategy` refers to the class name within the strategy file `test_ python3 ./freqtrade/main.py backtesting --export trades ``` -The exported trades can be read using the following code for manual analysis, or can be used by the plotting script `plot_dataframe.py` in the scripts folder. - -``` python -import json -from pathlib import Path -import pandas as pd - -filename=Path('user_data/backtest_data/backtest-result.json') - -with filename.open() as file: - data = json.load(file) - -columns = ["pair", "profit", "opents", "closets", "index", "duration", - "open_rate", "close_rate", "open_at_end", "sell_reason"] -df = pd.DataFrame(data, columns=columns) - -df['opents'] = pd.to_datetime(df['opents'], - unit='s', - utc=True, - infer_datetime_format=True - ) -df['closets'] = pd.to_datetime(df['closets'], - unit='s', - utc=True, - infer_datetime_format=True - ) -``` - -If you have some ideas for interesting / helpful backtest data analysis, feel free to submit a PR so the community can benefit from it. +The exported trades can be used for [further analysis](#further-backtest-result-analysis), or can be used by the plotting script `plot_dataframe.py` in the scripts folder. #### Exporting trades to file specifying a custom filename @@ -263,7 +235,9 @@ df.groupby("pair")["sell_reason"].value_counts() ``` -This will allow you to drill deeper into your backtest results, and perform analysis which would make the regular backtest-output unreadable. +This will allow you to drill deeper into your backtest results, and perform analysis which would make the regular backtest-output unreadable. + +If you have some ideas for interesting / helpful backtest data analysis ideas, please submit a PR so the community can benefit from it. ## Backtesting multiple strategies From a467d768326e4c6707d1b858e91fc3d0b3687448 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 19:35:25 +0100 Subject: [PATCH 07/17] Add /stopbuy command to telegram fixes #1607 --- freqtrade/rpc/rpc.py | 10 ++++++++++ freqtrade/rpc/telegram.py | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index af64c3d67..eb0269b5b 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -328,6 +328,16 @@ class RPC(object): self._freqtrade.state = State.RELOAD_CONF return {'status': 'reloading config ...'} + def _rpc_stopbuy(self) -> Dict[str, str]: + """ + Handler to stop buying, but handle open trades gracefully. + """ + if self._freqtrade.state == State.RUNNING: + # Set 'max_open_trades' to 0 + self._freqtrade.config['max_open_trades'] = 0 + + return {'status': 'Setting max_open_trades to 0. Run /reload_conf to reset.'} + def _rpc_forcesell(self, trade_id) -> None: """ Handler for forcesell . diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index e599172e4..6771ec803 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -91,6 +91,7 @@ class Telegram(RPC): CommandHandler('daily', self._daily), CommandHandler('count', self._count), CommandHandler('reload_conf', self._reload_conf), + CommandHandler('stopbuy', self._stopbuy), CommandHandler('whitelist', self._whitelist), CommandHandler('help', self._help), CommandHandler('version', self._version), @@ -362,6 +363,18 @@ class Telegram(RPC): msg = self._rpc_reload_conf() self._send_msg('Status: `{status}`'.format(**msg), bot=bot) + @authorized_only + def _stopbuy(self, bot: Bot, update: Update) -> None: + """ + Handler for /stop_buy. + Sets max_open_trades to 0 and gracefully sells all open trades + :param bot: telegram bot + :param update: message update + :return: None + """ + msg = self._rpc_stopbuy() + self._send_msg('Status: `{status}`'.format(**msg), bot=bot) + @authorized_only def _forcesell(self, bot: Bot, update: Update) -> None: """ @@ -481,6 +494,7 @@ class Telegram(RPC): "*/count:* `Show number of trades running compared to allowed number of trades`" \ "\n" \ "*/balance:* `Show account balance per currency`\n" \ + "*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" \ "*/reload_conf:* `Reload configuration file` \n" \ "*/whitelist:* `Show current whitelist` \n" \ "*/help:* `This help message`\n" \ From 9373d0c915d14795aa832288e26fa14a5e2975bc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 19:36:02 +0100 Subject: [PATCH 08/17] Add tests for /stopbuy --- freqtrade/tests/rpc/test_rpc.py | 20 ++++++++++++++++++++ freqtrade/tests/rpc/test_rpc_telegram.py | 22 +++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index e1261e02e..e5ac79feb 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -406,6 +406,26 @@ def test_rpc_stop(mocker, default_conf) -> None: assert freqtradebot.state == State.STOPPED +def test_rpc_stopbuy(mocker, default_conf) -> None: + patch_coinmarketcap(mocker) + patch_exchange(mocker) + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_ticker=MagicMock() + ) + + freqtradebot = FreqtradeBot(default_conf) + patch_get_signal(freqtradebot, (True, False)) + rpc = RPC(freqtradebot) + freqtradebot.state = State.RUNNING + + assert freqtradebot.config['max_open_trades'] != 0 + result = rpc._rpc_stopbuy() + assert {'status': 'Setting max_open_trades to 0. Run /reload_conf to reset.'} == result + assert freqtradebot.config['max_open_trades'] == 0 + + def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None: patch_coinmarketcap(mocker) patch_exchange(mocker) diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 02f4f4afb..dd8215551 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -74,7 +74,7 @@ def test_init(default_conf, mocker, caplog) -> None: message_str = "rpc.telegram is listening for following commands: [['status'], ['profit'], " \ "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], " \ "['performance'], ['daily'], ['count'], ['reload_conf'], " \ - "['whitelist'], ['help'], ['version']]" + "['stopbuy'], ['whitelist'], ['help'], ['version']]" assert log_has(message_str, caplog.record_tuples) @@ -662,6 +662,26 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None: assert 'already stopped' in msg_mock.call_args_list[0][0][0] +def test_stopbuy_handle(default_conf, update, mocker) -> None: + patch_coinmarketcap(mocker) + msg_mock = MagicMock() + mocker.patch.multiple( + 'freqtrade.rpc.telegram.Telegram', + _init=MagicMock(), + _send_msg=msg_mock + ) + + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + telegram = Telegram(freqtradebot) + + assert freqtradebot.config['max_open_trades'] != 0 + telegram._stopbuy(bot=MagicMock(), update=update) + assert freqtradebot.config['max_open_trades'] == 0 + assert msg_mock.call_count == 1 + assert 'Setting max_open_trades to 0. Run /reload_conf to reset.' \ + in msg_mock.call_args_list[0][0][0] + + def test_reload_conf_handle(default_conf, update, mocker) -> None: patch_coinmarketcap(mocker) msg_mock = MagicMock() From 37e6b262eb06f923e7d4c8955918ca23073b7263 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 19:36:25 +0100 Subject: [PATCH 09/17] Update docs to include /stopbuy --- docs/telegram-usage.md | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index db98cbb12..face22404 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -16,6 +16,7 @@ official commands. You can ask at any moment for help with `/help`. |----------|---------|-------------| | `/start` | | Starts the trader | `/stop` | | Stops the trader +| `/stopbuy` | | Stops the trader from opening new trades. Gracefully closes open trades according to their rules. | `/reload_conf` | | Reloads the configuration file | `/status` | | Lists all open trades | `/status table` | | List all open trades in a table format @@ -43,7 +44,21 @@ Below, example of Telegram message you will receive for each command. > `Stopping trader ...` > **Status:** `stopped` -## /status +### /stopbuy + +> **status:** `Setting max_open_trades to 0. Run /reload_conf to reset.` + +Prevents the bot from opening new trades by temporarily setting "max_open_trades" to 0. Open trades will be handled via their regular rules (ROI / Sell-signal, stoploss, ...). + +After this, give the bot time to close off open trades (can be checked via `/status table`). +Once all positions are sold, run `/stop` to completely stop the bot. + +`/reload_conf` resets "max_open_trades" to the value set in the configuration and resets this command. + +!!! warning: +The stop-buy signal is ONLY active while the bot is running, and is not persisted anyway, so restarting the bot will cause this to reset. + +### /status For each open trade, the bot will send you the following message. @@ -58,7 +73,7 @@ For each open trade, the bot will send you the following message. > **Current Profit:** `12.95%` > **Open Order:** `None` -## /status table +### /status table Return the status of all open trades in a table format. ``` @@ -68,7 +83,7 @@ Return the status of all open trades in a table format. 123 CVC/BTC 1 h 12.95% ``` -## /count +### /count Return the number of trades used and available. ``` @@ -77,7 +92,7 @@ current max 2 10 ``` -## /profit +### /profit Return a summary of your profit/loss and performance. @@ -94,11 +109,11 @@ Return a summary of your profit/loss and performance. > **Avg. Duration:** `2:33:45` > **Best Performing:** `PAY/BTC: 50.23%` -## /forcesell +### /forcesell > **BITTREX:** Selling BTC/LTC with limit `0.01650000 (profit: ~-4.07%, -0.00008168)` -## /forcebuy +### /forcebuy > **BITTREX**: Buying ETH/BTC with limit `0.03400000` (`1.000000 ETH`, `225.290 USD`) @@ -106,7 +121,7 @@ Note that for this to work, `forcebuy_enable` needs to be set to true. [More details](configuration.md/#understand-forcebuy_enable) -## /performance +### /performance Return the performance of each crypto-currency the bot has sold. > Performance: @@ -117,7 +132,7 @@ Return the performance of each crypto-currency the bot has sold. > 5. `STORJ/BTC 27.24%` > ... -## /balance +### /balance Return the balance of all crypto-currency your have on the exchange. @@ -131,7 +146,7 @@ Return the balance of all crypto-currency your have on the exchange. > **Balance:** 86.64180098 > **Pending:** 0.0 -## /daily +### /daily Per default `/daily` will return the 7 last days. The example below if for `/daily 3`: @@ -145,6 +160,6 @@ Day Profit BTC Profit USD 2018-01-01 0.00269130 BTC 34.986 USD ``` -## /version +### /version > **Version:** `0.14.3` From aa698a84125bf03106338356ffaea18517c023f3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 18 Mar 2019 06:27:44 +0100 Subject: [PATCH 10/17] rename /stopbuy message --- freqtrade/rpc/rpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index eb0269b5b..7ad51fa8e 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -336,7 +336,7 @@ class RPC(object): # Set 'max_open_trades' to 0 self._freqtrade.config['max_open_trades'] = 0 - return {'status': 'Setting max_open_trades to 0. Run /reload_conf to reset.'} + return {'status': 'No more buy will occur from now. Run /reload_conf to reset.'} def _rpc_forcesell(self, trade_id) -> None: """ From 8d173efe2d48d75a07a5bcbfad085b4dc31c7179 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 18 Mar 2019 06:28:58 +0100 Subject: [PATCH 11/17] reword stopbuy message --- freqtrade/tests/rpc/test_rpc.py | 2 +- freqtrade/tests/rpc/test_rpc_telegram.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index e5ac79feb..baddc0685 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -422,7 +422,7 @@ def test_rpc_stopbuy(mocker, default_conf) -> None: assert freqtradebot.config['max_open_trades'] != 0 result = rpc._rpc_stopbuy() - assert {'status': 'Setting max_open_trades to 0. Run /reload_conf to reset.'} == result + assert {'status': 'No more buy will occur from now. Run /reload_conf to reset.'} == result assert freqtradebot.config['max_open_trades'] == 0 diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index dd8215551..8e8d1f1bb 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -678,7 +678,7 @@ def test_stopbuy_handle(default_conf, update, mocker) -> None: telegram._stopbuy(bot=MagicMock(), update=update) assert freqtradebot.config['max_open_trades'] == 0 assert msg_mock.call_count == 1 - assert 'Setting max_open_trades to 0. Run /reload_conf to reset.' \ + assert 'No more buy will occur from now. Run /reload_conf to reset.' \ in msg_mock.call_args_list[0][0][0] From 50ea4c39da3a33bdeaa579de747bc23af2e2a011 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 18 Mar 2019 13:32:05 +0100 Subject: [PATCH 12/17] Update ccxt from 1.18.368 to 1.18.372 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 400940d16..85b43b4f8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.368 +ccxt==1.18.372 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From 9a6106736763c89bdc8709d28b4f32953b35b30f Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 19 Mar 2019 13:32:04 +0100 Subject: [PATCH 13/17] Update ccxt from 1.18.372 to 1.18.376 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 85b43b4f8..7f3fe503d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.372 +ccxt==1.18.376 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From 2b09e3ca3db6490df5d633275f634618ab147057 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 19 Mar 2019 13:32:05 +0100 Subject: [PATCH 14/17] Update plotly from 3.7.0 to 3.7.1 --- requirements-plot.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-plot.txt b/requirements-plot.txt index b49aad626..e582fddf6 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,5 +1,5 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==3.7.0 +plotly==3.7.1 From 6c889895bdf53eb5b4a4324178de5d87b48a624a Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 20 Mar 2019 13:35:05 +0100 Subject: [PATCH 15/17] Update ccxt from 1.18.376 to 1.18.385 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7f3fe503d..3a25bb8bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.376 +ccxt==1.18.385 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From cc369f41f550e804f111b04ad316e8fe7d362c57 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 20 Mar 2019 13:35:07 +0100 Subject: [PATCH 16/17] Update coveralls from 1.6.0 to 1.7.0 --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 68d63101a..e0aaf9461 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,5 +8,5 @@ pytest==4.3.1 pytest-mock==1.10.1 pytest-asyncio==0.10.0 pytest-cov==2.6.1 -coveralls==1.6.0 +coveralls==1.7.0 mypy==0.670 From 00821036bb21c9d80265d5a5ba0e0a310f9b666b Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Wed, 20 Mar 2019 23:57:49 +0300 Subject: [PATCH 17/17] docs for dry_run_wallet --- docs/configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/configuration.md b/docs/configuration.md index e7ad9c9bf..4b8d990fe 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -20,6 +20,7 @@ Mandatory Parameters are marked as **Required**. | `ticker_interval` | [1m, 5m, 15m, 30m, 1h, 1d, ...] | The ticker interval to use (1min, 5 min, 15 min, 30 min, 1 hour or 1 day). Default is 5 minutes. [Strategy Override](#parameters-in-the-strategy). | `fiat_display_currency` | USD | **Required.** Fiat currency used to show your profits. More information below. | `dry_run` | true | **Required.** Define if the bot must be in Dry-run or production mode. +| `dry_run_wallet` | 999.9 | Overrides the default amount of 999.9 stake currency units in the wallet used by the bot running in the Dry Run mode if you need it for any reason. | `process_only_new_candles` | false | If set to true indicators are processed only once a new candle arrives. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy). | `minimal_roi` | See below | Set the threshold in percent the bot will use to sell a trade. More information below. [Strategy Override](#parameters-in-the-strategy). | `stoploss` | -0.10 | Value of the stoploss in percent used by the bot. More information below. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).