From 931d24b5a8c028b879518921c0c67f79865b9dcf Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Dec 2019 09:22:15 +0100 Subject: [PATCH 01/11] Have dry_run_wallet default to 1000 --- docs/configuration.md | 2 +- freqtrade/constants.py | 5 +++-- freqtrade/exchange/exchange.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 5ad1a886e..927432a46 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -47,7 +47,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `ticker_interval` | The ticker interval to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *String* | `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency).
***Datatype:*** *String* | `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode.
*Defaults to `true`.*
***Datatype:*** *Boolean* -| `dry_run_wallet` | 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.
***Datatype:*** *Float* +| `dry_run_wallet` | Overrides the default amount of 1000 stake currency units in the wallet used by the bot running in the Dry Run mode if you need it for any reason.
***Datatype:*** *Float* | `process_only_new_candles` | Enable processing of indicators only when new candles arrive. 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).
*Defaults to `false`.*
***Datatype:*** *Boolean* | `minimal_roi` | **Required.** Set the threshold in percent the bot will use to sell a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Dict* | `stoploss` | **Required.** Value of the stoploss in percent used by the bot. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Float (as ratio)* diff --git a/freqtrade/constants.py b/freqtrade/constants.py index f5e5969eb..5c7190b41 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -18,7 +18,7 @@ REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange'] ORDERTYPE_POSSIBILITIES = ['limit', 'market'] ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'PrecisionFilter', 'PriceFilter'] -DRY_RUN_WALLET = 999.9 +DRY_RUN_WALLET = 1000 MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons USERPATH_HYPEROPTS = 'hyperopts' @@ -75,7 +75,7 @@ CONF_SCHEMA = { }, 'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT}, 'dry_run': {'type': 'boolean'}, - 'dry_run_wallet': {'type': 'number'}, + 'dry_run_wallet': {'type': 'number', 'default': DRY_RUN_WALLET}, 'process_only_new_candles': {'type': 'boolean'}, 'minimal_roi': { 'type': 'object', @@ -275,6 +275,7 @@ CONF_SCHEMA = { 'stake_currency', 'stake_amount', 'dry_run', + 'dry_run_wallet', 'bid_strategy', 'unfilledtimeout', 'stoploss', diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index fbe7cd29a..a148f9dae 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -479,7 +479,7 @@ class Exchange: @retrier def get_balance(self, currency: str) -> float: if self._config['dry_run']: - return constants.DRY_RUN_WALLET + return self._config['dry_run_wallet'] # ccxt exception is already handled by get_balances balances = self.get_balances() From 52b212db64ae6d679f76f6e9c0af382f54b13751 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Dec 2019 09:37:08 +0100 Subject: [PATCH 02/11] Fix tests after changing dry_run_wallet amount --- tests/exchange/test_exchange.py | 1 + tests/test_configuration.py | 7 ++++--- tests/test_freqtradebot.py | 2 ++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index a21a5f3ac..774ad8cf2 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -876,6 +876,7 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name): def test_get_balance_dry_run(default_conf, mocker): default_conf['dry_run'] = True + default_conf['dry_run_wallet'] = 999.9 exchange = get_patched_exchange(mocker, default_conf) assert exchange.get_balance(currency='BTC') == 999.9 diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 89ca74afa..292d53315 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -8,9 +8,9 @@ from pathlib import Path from unittest.mock import MagicMock import pytest -from jsonschema import Draft4Validator, ValidationError, validate +from jsonschema import ValidationError -from freqtrade import OperationalException, constants +from freqtrade import OperationalException from freqtrade.configuration import (Arguments, Configuration, check_exchange, remove_credentials, validate_config_consistency) @@ -718,7 +718,8 @@ def test_load_config_warn_forcebuy(default_conf, mocker, caplog) -> None: def test_validate_default_conf(default_conf) -> None: - validate(default_conf, constants.CONF_SCHEMA, Draft4Validator) + # Validate via our validator - we allow setting defaults! + validate_config_schema(default_conf) def test_validate_tsl(default_conf): diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 5e197da71..a73fd6c61 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -211,6 +211,7 @@ def test_edge_overrides_stake_amount(mocker, edge_conf) -> None: patch_RPCManager(mocker) patch_exchange(mocker) patch_edge(mocker) + edge_conf['dry_run_wallet'] = 999.9 freqtrade = FreqtradeBot(edge_conf) assert freqtrade._get_trade_stake_amount('NEO/BTC') == (999.9 * 0.5 * 0.01) / 0.20 @@ -1338,6 +1339,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, patch_exchange(mocker) patch_edge(mocker) edge_conf['max_open_trades'] = float('inf') + edge_conf['dry_run_wallet'] = 999.9 mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_ticker=MagicMock(return_value={ From fda8f7e30599810a834e2442a484d714e2ba3463 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Dec 2019 09:38:18 +0100 Subject: [PATCH 03/11] Introuce WalletDry - supporting dry-run wallets --- freqtrade/freqtradebot.py | 13 ++++++++----- freqtrade/rpc/telegram.py | 6 ++++++ freqtrade/wallets.py | 41 ++++++++++++++++++++++++++++++--------- 3 files changed, 46 insertions(+), 14 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 0595e0d35..df9fd0b17 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -25,7 +25,7 @@ from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.pairlist.pairlistmanager import PairListManager from freqtrade.state import State from freqtrade.strategy.interface import IStrategy, SellType -from freqtrade.wallets import Wallets +from freqtrade.wallets import Wallets, WalletsDry logger = logging.getLogger(__name__) @@ -62,7 +62,13 @@ class FreqtradeBot: self.exchange = ExchangeResolver(self.config['exchange']['name'], self.config).exchange - self.wallets = Wallets(self.config, self.exchange) + persistence.init(self.config.get('db_url', None), + clean_open_orders=self.config.get('dry_run', False)) + + if self.config['dry_run']: + self.wallets = WalletsDry(self.config, self.exchange) + else: + self.wallets = Wallets(self.config, self.exchange) self.dataprovider = DataProvider(self.config, self.exchange) # Attach Dataprovider to Strategy baseclass @@ -78,9 +84,6 @@ class FreqtradeBot: self.active_pair_whitelist = self._refresh_whitelist() - persistence.init(self.config.get('db_url', None), - clean_open_orders=self.config.get('dry_run', False)) - # Set initial bot state from config initial_state = self.config.get('initial_state') self.state = State[initial_state.upper()] if initial_state else State.STOPPED diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 2e736f11a..4d7857f44 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -331,7 +331,13 @@ class Telegram(RPC): try: result = self._rpc_balance(self._config['stake_currency'], self._config.get('fiat_display_currency', '')) + output = '' + if self._config['dry_run']: + output += ( + f"Simulated balances - starting capital: " + f"`{self._config['dry_run_wallet']}` {self._config['stake_currency']}.\n" + ) for currency in result['currencies']: if currency['est_stake'] > 0.0001: curr_output = "*{currency}:*\n" \ diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index c674b5286..eb2603776 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -28,9 +28,6 @@ class Wallets: def get_free(self, currency) -> float: - if self._config['dry_run']: - return self._config.get('dry_run_wallet', constants.DRY_RUN_WALLET) - balance = self._wallets.get(currency) if balance and balance.free: return balance.free @@ -39,9 +36,6 @@ class Wallets: def get_used(self, currency) -> float: - if self._config['dry_run']: - return self._config.get('dry_run_wallet', constants.DRY_RUN_WALLET) - balance = self._wallets.get(currency) if balance and balance.used: return balance.used @@ -50,9 +44,6 @@ class Wallets: def get_total(self, currency) -> float: - if self._config['dry_run']: - return self._config.get('dry_run_wallet', constants.DRY_RUN_WALLET) - balance = self._wallets.get(currency) if balance and balance.total: return balance.total @@ -75,3 +66,35 @@ class Wallets: def get_all_balances(self) -> Dict[str, Any]: return self._wallets + + +class WalletsDry(Wallets): + + def __init__(self, config: dict, exchange: Exchange) -> None: + self.start_cap = config['dry_run_wallet'] + super().__init__(config, exchange) + + def update(self) -> None: + """ Update does not do anything in dry-mode...""" + from freqtrade.persistence import Trade + closed_trades = Trade.get_trades(Trade.is_open.is_(False)).all() + print(len(closed_trades)) + tot_profit = sum([trade.calc_profit() for trade in closed_trades]) + current_stake = self.start_cap + tot_profit + self._wallets[self._config['stake_currency']] = Wallet( + self._config['stake_currency'], + current_stake, + 0, + current_stake + ) + open_trades = Trade.get_trades(Trade.is_open.is_(True)).all() + + for trade in open_trades: + curr = trade.pair.split('/')[0] + trade.amount + self._wallets[curr] = Wallet( + curr, + trade.amount, + 0, + trade.amount + ) From f0bbc75038134d7debb9aed56d85eb2c2702c6b5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Dec 2019 09:48:35 +0100 Subject: [PATCH 04/11] Combine dry_run wallet into original Wallets class --- freqtrade/freqtradebot.py | 8 ++--- freqtrade/rpc/telegram.py | 2 +- freqtrade/wallets.py | 64 +++++++++++++++++++++------------------ 3 files changed, 39 insertions(+), 35 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index df9fd0b17..a242c11ae 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -25,7 +25,7 @@ from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.pairlist.pairlistmanager import PairListManager from freqtrade.state import State from freqtrade.strategy.interface import IStrategy, SellType -from freqtrade.wallets import Wallets, WalletsDry +from freqtrade.wallets import Wallets logger = logging.getLogger(__name__) @@ -65,10 +65,8 @@ class FreqtradeBot: persistence.init(self.config.get('db_url', None), clean_open_orders=self.config.get('dry_run', False)) - if self.config['dry_run']: - self.wallets = WalletsDry(self.config, self.exchange) - else: - self.wallets = Wallets(self.config, 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/rpc/telegram.py b/freqtrade/rpc/telegram.py index 4d7857f44..e36b46ba7 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -335,7 +335,7 @@ class Telegram(RPC): output = '' if self._config['dry_run']: output += ( - f"Simulated balances - starting capital: " + f"*Warning:*Simulated balances in Dry Mode.\nStarting capital: " f"`{self._config['dry_run_wallet']}` {self._config['stake_currency']}.\n" ) for currency in result['currencies']: diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index eb2603776..f8dd0ee2f 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -4,7 +4,7 @@ import logging from typing import Dict, NamedTuple, Any from freqtrade.exchange import Exchange -from freqtrade import constants +from freqtrade.persistence import Trade logger = logging.getLogger(__name__) @@ -23,6 +23,7 @@ class Wallets: self._config = config self._exchange = exchange self._wallets: Dict[str, Wallet] = {} + self.start_cap = config['dry_run_wallet'] self.update() @@ -50,36 +51,12 @@ class Wallets: else: return 0 - def update(self) -> None: - - balances = self._exchange.get_balances() - - for currency in balances: - self._wallets[currency] = Wallet( - currency, - balances[currency].get('free', None), - balances[currency].get('used', None), - balances[currency].get('total', None) - ) - - logger.info('Wallets synced.') - - def get_all_balances(self) -> Dict[str, Any]: - return self._wallets - - -class WalletsDry(Wallets): - - def __init__(self, config: dict, exchange: Exchange) -> None: - self.start_cap = config['dry_run_wallet'] - super().__init__(config, exchange) - - def update(self) -> None: - """ Update does not do anything in dry-mode...""" - from freqtrade.persistence import Trade + def _update_dry(self) -> None: + """ Update from database in dry-run mode""" closed_trades = Trade.get_trades(Trade.is_open.is_(False)).all() - print(len(closed_trades)) + tot_profit = sum([trade.calc_profit() for trade in closed_trades]) + current_stake = self.start_cap + tot_profit self._wallets[self._config['stake_currency']] = Wallet( self._config['stake_currency'], @@ -98,3 +75,32 @@ class WalletsDry(Wallets): 0, trade.amount ) + + def _update_live(self) -> None: + + balances = self._exchange.get_balances() + + for currency in balances: + self._wallets[currency] = Wallet( + currency, + balances[currency].get('free', None), + balances[currency].get('used', None), + balances[currency].get('total', None) + ) + + def update(self) -> None: + if self._config['dry_run']: + self._update_dry() + else: + self._update_live() + logger.info('Wallets synced.') + + def get_all_balances(self) -> Dict[str, Any]: + return self._wallets + + +class WalletsDry(Wallets): + + def __init__(self, config: dict, exchange: Exchange) -> None: + + super().__init__(config, exchange) From 5a5741878cecbadae067c8ee0c29b211161a3aeb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Dec 2019 10:26:56 +0100 Subject: [PATCH 05/11] Improve dry-run calculations --- freqtrade/freqtradebot.py | 4 ++-- freqtrade/wallets.py | 7 +++---- tests/test_freqtradebot.py | 30 ++++++++++++++++++++++++++++++ tests/test_integration.py | 1 + 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a242c11ae..5c3ef64b1 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -232,8 +232,8 @@ class FreqtradeBot: # Check if stake_amount is fulfilled if available_amount < stake_amount: raise DependencyException( - f"Available balance({available_amount} {self.config['stake_currency']}) is " - f"lower than stake amount({stake_amount} {self.config['stake_currency']})" + f"Available balance ({available_amount} {self.config['stake_currency']}) is " + f"lower than stake amount ({stake_amount} {self.config['stake_currency']})" ) return stake_amount diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index f8dd0ee2f..9ee305aab 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -54,21 +54,20 @@ class Wallets: def _update_dry(self) -> None: """ Update from database in dry-run mode""" closed_trades = Trade.get_trades(Trade.is_open.is_(False)).all() - + open_trades = Trade.get_trades(Trade.is_open.is_(True)).all() tot_profit = sum([trade.calc_profit() for trade in closed_trades]) + tot_in_trades = sum([trade.stake_amount for trade in open_trades]) - current_stake = self.start_cap + tot_profit + current_stake = self.start_cap + tot_profit - tot_in_trades self._wallets[self._config['stake_currency']] = Wallet( self._config['stake_currency'], current_stake, 0, current_stake ) - open_trades = Trade.get_trades(Trade.is_open.is_(True)).all() for trade in open_trades: curr = trade.pair.split('/')[0] - trade.amount self._wallets[curr] = Wallet( curr, trade.amount, diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index a73fd6c61..a60ea8c7c 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -3485,3 +3485,33 @@ def test_process_i_am_alive(default_conf, mocker, caplog): ftbot.process() assert log_has_re(message, caplog) + + +@pytest.mark.usefixtures("init_persistence") +def test_sync_wallet_dry_run(mocker, default_conf, ticker, fee, limit_buy_order): + default_conf['dry_run'] = True + # Initialize to 2 times stake amount + default_conf['dry_run_wallet'] = 0.002 + default_conf['max_open_trades'] = 2 + patch_exchange(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_ticker=ticker, + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + get_fee=fee, + ) + + bot = get_patched_freqtradebot(mocker, default_conf) + patch_get_signal(bot) + assert bot.wallets.get_free('BTC') == 0.002 + + bot.create_trades() + trades = Trade.query.all() + assert len(trades) == 2 + + bot.config['max_open_trades'] = 3 + with pytest.raises( + DependencyException, + match=r"Available balance \(0 BTC\) is lower than stake amount \(0.001 BTC\)"): + bot.create_trades() + diff --git a/tests/test_integration.py b/tests/test_integration.py index 228ed8468..728e96d55 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -71,6 +71,7 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee, ) mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock) wallets_mock = mocker.patch("freqtrade.wallets.Wallets.update", MagicMock()) + mocker.patch("freqtrade.wallets.Wallets.get_free", MagicMock(return_value=1)) freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade.strategy.order_types['stoploss_on_exchange'] = True From c741b67c3c1be52872ec12273504af0a68c1975f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Dec 2019 10:39:52 +0100 Subject: [PATCH 06/11] Adjust tests for dry_run wallet simulation --- freqtrade/exchange/exchange.py | 2 +- tests/rpc/test_rpc.py | 2 +- tests/rpc/test_rpc_apiserver.py | 1 + tests/rpc/test_rpc_telegram.py | 6 ++++-- tests/test_freqtradebot.py | 1 - tests/test_wallets.py | 3 ++- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index a148f9dae..2401c59b6 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -18,7 +18,7 @@ from ccxt.base.decimal_to_precision import ROUND_DOWN, ROUND_UP from pandas import DataFrame from freqtrade import (DependencyException, InvalidOrderException, - OperationalException, TemporaryError, constants) + OperationalException, TemporaryError) from freqtrade.data.converter import parse_ticker_dataframe from freqtrade.exchange.common import BAD_EXCHANGES, retrier, retrier_async from freqtrade.misc import deep_merge_dicts diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 699f2d962..0a8c1cabd 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -398,7 +398,7 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): get_valid_pair_combination=MagicMock( side_effect=lambda a, b: f"{b}/{a}" if a == "USDT" else f"{a}/{b}") ) - + default_conf['dry_run'] = False freqtradebot = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 555fcdc81..ebb70bdf8 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -230,6 +230,7 @@ def test_api_stopbuy(botclient): def test_api_balance(botclient, mocker, rpc_balance): ftbot, client = botclient + ftbot.config['dry_run'] = False mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance) mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', side_effect=lambda a, b: f"{a}/{b}") diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 2ba1ccf4b..b02f11394 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -462,7 +462,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tickers) -> None: - + default_conf['dry_run'] = False mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance) mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', @@ -494,6 +494,7 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick def test_balance_handle_empty_response(default_conf, update, mocker) -> None: + default_conf['dry_run'] = False mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={}) msg_mock = MagicMock() @@ -533,7 +534,8 @@ def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None telegram._balance(update=update, context=MagicMock()) result = msg_mock.call_args_list[0][0][0] assert msg_mock.call_count == 1 - assert "Running in Dry Run, balances are not available." in result + assert "*Warning:*Simulated balances in Dry Mode." in result + assert "Starting capital: `1000` BTC" in result def test_balance_handle_too_large_response(default_conf, update, mocker) -> None: diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index a60ea8c7c..18f5a461a 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -3514,4 +3514,3 @@ def test_sync_wallet_dry_run(mocker, default_conf, ticker, fee, limit_buy_order) DependencyException, match=r"Available balance \(0 BTC\) is lower than stake amount \(0.001 BTC\)"): bot.create_trades() - diff --git a/tests/test_wallets.py b/tests/test_wallets.py index ae2810a2d..3177edc05 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -1,7 +1,8 @@ # pragma pylint: disable=missing-docstring -from tests.conftest import get_patched_freqtradebot from unittest.mock import MagicMock +from tests.conftest import get_patched_freqtradebot + def test_sync_wallet_at_boot(mocker, default_conf): default_conf['dry_run'] = False From 23d467eb0d827f6abd6641b786fe1e2a72da2e9c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Dec 2019 10:41:57 +0100 Subject: [PATCH 07/11] Show simulation note also in restserver --- freqtrade/rpc/rpc.py | 1 + freqtrade/rpc/telegram.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 4cebe646e..84b72fe18 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -348,6 +348,7 @@ class RPC: 'total': total, 'symbol': symbol, 'value': value, + 'note': 'Simulated balances' if self._freqtrade.config.get('dry_run', False) else '' } def _rpc_start(self) -> Dict[str, str]: diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index e36b46ba7..c1572bb39 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -335,7 +335,9 @@ class Telegram(RPC): output = '' if self._config['dry_run']: output += ( - f"*Warning:*Simulated balances in Dry Mode.\nStarting capital: " + f"*Warning:*Simulated balances in Dry Mode.\n" + "This mode is still experimental!\n" + "Starting capital: " f"`{self._config['dry_run_wallet']}` {self._config['stake_currency']}.\n" ) for currency in result['currencies']: From 56e13c8919319997e99835ba645c7c3c175779ad Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Dec 2019 10:55:15 +0100 Subject: [PATCH 08/11] Enhance documentation for dry-run wallet --- docs/configuration.md | 10 ++++++---- freqtrade/wallets.py | 7 ------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 927432a46..f0724abc4 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -47,7 +47,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `ticker_interval` | The ticker interval to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *String* | `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency).
***Datatype:*** *String* | `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode.
*Defaults to `true`.*
***Datatype:*** *Boolean* -| `dry_run_wallet` | Overrides the default amount of 1000 stake currency units in the wallet used by the bot running in the Dry Run mode if you need it for any reason.
***Datatype:*** *Float* +| `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in the Dry Run mode.
*Defaults to `1000`.*
***Datatype:*** *Float* | `process_only_new_candles` | Enable processing of indicators only when new candles arrive. 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).
*Defaults to `false`.*
***Datatype:*** *Boolean* | `minimal_roi` | **Required.** Set the threshold in percent the bot will use to sell a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Dict* | `stoploss` | **Required.** Value of the stoploss in percent used by the bot. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Float (as ratio)* @@ -498,8 +498,10 @@ creating trades on the exchange. } ``` -Once you will be happy with your bot performance running in the Dry-run mode, -you can switch it to production mode. +Once you will be happy with your bot performance running in the Dry-run mode, you can switch it to production mode. + +!!! Note + A simulated wallet is available during dry-run mode, and will assume a starting capital of `dry_run_wallet` (defaults to 1000). ## Switch to production mode @@ -529,7 +531,7 @@ you run it in production mode. ``` !!! Note - If you have an exchange API key yet, [see our tutorial](/pre-requisite). + If you have an exchange API key yet, [see our tutorial](installation.md#setup-your-exchange-account). You should also make sure to read the [Exchanges](exchanges.md) section of the documentation to be aware of potential configuration details specific to your exchange. diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 9ee305aab..4c3a0f657 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -96,10 +96,3 @@ class Wallets: def get_all_balances(self) -> Dict[str, Any]: return self._wallets - - -class WalletsDry(Wallets): - - def __init__(self, config: dict, exchange: Exchange) -> None: - - super().__init__(config, exchange) From b5b6458f128a13920e5b0d7bf3f1fe1b084ead22 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Dec 2019 10:57:27 +0100 Subject: [PATCH 09/11] Add note about unlimited stake amount --- docs/configuration.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index f0724abc4..9490927df 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -149,6 +149,9 @@ In this case a trade amount is calculated as: currency_balance / (max_open_trades - current_open_trades) ``` +!!! Note "When using Dry-Run Mode" + When using `"stake_amount" : "unlimited",` in combination with Dry-Run, the balance will be simulated starting with a stake of `dry_run_wallet` which will evolve over time. It is therefore important to set `dry_run_wallet` to a sensible value, otherwise it may simulate trades with 100 BTC (or more) at once - which may not correspond to your real available balance. + ### Understand minimal_roi The `minimal_roi` configuration parameter is a JSON object where the key is a duration From ce845ab092e38448cf5f04443d36c004d4feb2c5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Dec 2019 11:03:40 +0100 Subject: [PATCH 10/11] Improve docstring for dry-run wallet method --- freqtrade/wallets.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 4c3a0f657..dd706438f 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -52,7 +52,12 @@ class Wallets: return 0 def _update_dry(self) -> None: - """ Update from database in dry-run mode""" + """ + Update from database in dry-run mode + - Apply apply profits of closed trades on top of stake amount + - Subtract currently tied up stake_amount in open trades + - update balances for currencies currently in trades + """ closed_trades = Trade.get_trades(Trade.is_open.is_(False)).all() open_trades = Trade.get_trades(Trade.is_open.is_(True)).all() tot_profit = sum([trade.calc_profit() for trade in closed_trades]) From 655672c9575132ef5d143afd4ee38679e9d8b352 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 Dec 2019 06:22:54 +0100 Subject: [PATCH 11/11] Enhance documentation Note --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 9490927df..2c8f7cea7 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -150,7 +150,7 @@ currency_balance / (max_open_trades - current_open_trades) ``` !!! Note "When using Dry-Run Mode" - When using `"stake_amount" : "unlimited",` in combination with Dry-Run, the balance will be simulated starting with a stake of `dry_run_wallet` which will evolve over time. It is therefore important to set `dry_run_wallet` to a sensible value, otherwise it may simulate trades with 100 BTC (or more) at once - which may not correspond to your real available balance. + When using `"stake_amount" : "unlimited",` in combination with Dry-Run, the balance will be simulated starting with a stake of `dry_run_wallet` which will evolve over time. It is therefore important to set `dry_run_wallet` to a sensible value (like 0.05 or 0.01 for BTC and 1000 or 100 for USDT, for example), otherwise it may simulate trades with 100 BTC (or more) or 0.05 USDT (or less) at once - which may not correspond to your real available balance or is less than the exchange minimal limit for the order amount for the stake currency. ### Understand minimal_roi