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 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). 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` diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 0582e7d08..02062acc4 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/exchange/exchange.py b/freqtrade/exchange/exchange.py index e4838b74c..ea8bcfac1 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] = {} @@ -370,7 +370,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 @@ -383,7 +383,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 @@ -408,7 +408,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 @@ -423,8 +423,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() @@ -436,7 +436,7 @@ class Exchange(object): @retrier def get_balances(self) -> dict: - if self._conf['dry_run']: + if self._config['dry_run']: return {} try: @@ -607,7 +607,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: @@ -623,7 +623,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: @@ -660,7 +660,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 [] diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 39ef97f8b..6f1fb2c99 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -63,7 +63,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 @@ -613,6 +613,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['bids'][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. @@ -649,7 +668,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 @@ -892,7 +911,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..a0ffff107 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'] @@ -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': 'No more buy will occur from now. Run /reload_conf to reset.'} + def _rpc_forcesell(self, trade_id) -> None: """ Handler for forcesell . @@ -356,7 +366,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 ---- 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" \ diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index e1261e02e..baddc0685 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': 'No more buy will occur from now. 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..8e8d1f1bb 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 'No more buy will occur from now. 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() diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index fc7c48663..e4f0415f7 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.043936 + + def test_startup_messages(default_conf, mocker): default_conf['pairlist'] = {'method': 'VolumePairList', 'config': {'number_assets': 20} 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.') 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 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 diff --git a/requirements.txt b/requirements.txt index 400940d16..3a25bb8bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.368 +ccxt==1.18.385 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1