From 9be98cd8f77c503f303d021eb62b531c9be698b3 Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 23 May 2018 13:15:03 +0300 Subject: [PATCH] Add ability to set unlimited stake_amount --- docs/configuration.md | 2 +- freqtrade/constants.py | 5 +- freqtrade/freqtradebot.py | 25 ++++-- freqtrade/optimize/backtesting.py | 5 +- freqtrade/tests/test_freqtradebot.py | 130 ++++++++++++++++++++++----- 5 files changed, 137 insertions(+), 30 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 3d36947c3..e75775f3f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -16,7 +16,7 @@ The table below will list all configuration parameters. |----------|---------|----------|-------------| | `max_open_trades` | 3 | Yes | Number of trades open your bot will have. | `stake_currency` | BTC | Yes | Crypto-currency used for trading. -| `stake_amount` | 0.05 | Yes | Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. +| `stake_amount` | 0.05 | Yes | Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. 'unlimited' is used to allow a bot to use all avaliable balance. | `ticker_interval` | [1m, 5m, 30m, 1h, 1d] | No | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Default is 5 minutes | `fiat_display_currency` | USD | Yes | Fiat currency used to show your profits. More information below. | `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode. diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 7731ea610..20c8a2287 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -32,7 +32,10 @@ CONF_SCHEMA = { 'max_open_trades': {'type': 'integer', 'minimum': 0}, 'ticker_interval': {'type': 'string', 'enum': list(TICKER_INTERVAL_MINUTES.keys())}, 'stake_currency': {'type': 'string', 'enum': ['BTC', 'ETH', 'USDT']}, - 'stake_amount': {'type': 'number', 'minimum': 0.0005}, + 'stake_amount': {'anyOf': [ + {'type': 'integer', 'minimum': 0.0005}, + {'constant': 'unlimited'} + ]}, 'fiat_display_currency': {'type': 'string', 'enum': ['AUD', 'BRL', 'CAD', 'CHF', 'CLP', 'CNY', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD', 'HUF', diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 7c955d423..af3f9287e 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -255,6 +255,24 @@ class FreqtradeBot(object): balance = self.config['bid_strategy']['ask_last_balance'] return ticker['ask'] + balance * (ticker['last'] - ticker['ask']) + def _get_trade_stake_amount(self) -> float: + stake_amount = self.config['stake_amount'] + avaliable_amount = exchange.get_balance(self.config['stake_currency']) + + if stake_amount == 'unlimited': + open_trades = len(Trade.query.filter(Trade.is_open.is_(True)).all()) + if open_trades == self.config['max_open_trades']: + return 0 + return avaliable_amount / (self.config['max_open_trades'] - open_trades) + + # Check if stake_amount is fulfilled + if avaliable_amount < stake_amount: + raise DependencyException( + 'stake amount is not fulfilled (currency={})'.format(self.config['stake_currency']) + ) + + return stake_amount + def create_trade(self) -> bool: """ Checks the implemented trading indicator(s) for a randomly picked pair, @@ -263,19 +281,14 @@ class FreqtradeBot(object): :param interval: Ticker interval used for Analyze :return: True if a trade object has been created and persisted, False otherwise """ - stake_amount = self.config['stake_amount'] interval = self.analyze.get_ticker_interval() + stake_amount = self._get_trade_stake_amount() logger.info( 'Checking buy signals to create a new trade with stake_amount: %f ...', stake_amount ) whitelist = copy.deepcopy(self.config['exchange']['pair_whitelist']) - # Check if stake_amount is fulfilled - if exchange.get_balance(self.config['stake_currency']) < stake_amount: - raise DependencyException( - 'stake amount is not fulfilled (currency={})'.format(self.config['stake_currency']) - ) # Remove currently opened and latest pairs from whitelist for trade in Trade.query.filter(Trade.is_open.is_(True)).all(): diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 376730d0f..eb6b25e02 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -13,7 +13,7 @@ from pandas import DataFrame from tabulate import tabulate import freqtrade.optimize as optimize -from freqtrade import exchange +from freqtrade import exchange, DependencyException from freqtrade.analyze import Analyze from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration @@ -296,6 +296,9 @@ def setup_configuration(args: Namespace) -> Dict[str, Any]: config['exchange']['key'] = '' config['exchange']['secret'] = '' + if config['stake_amount'] == 'unlimited': + raise DependencyException('stake amount could not be "unlimited" for backtesting') + return config diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index ebabc0187..d9d009a47 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -220,6 +220,115 @@ def test_refresh_whitelist() -> None: pass +def test_get_trade_stake_amount(default_conf, ticker, limit_buy_order, fee, mocker) -> None: + """ + Test get_trade_stake_amount() method + """ + patch_get_signal(mocker) + patch_RPCManager(mocker) + patch_coinmarketcap(mocker) + mocker.patch.multiple( + 'freqtrade.freqtradebot.exchange', + validate_pairs=MagicMock(), + get_ticker=ticker, + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + get_fee=fee, + ) + + conf = deepcopy(default_conf) + + freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) + + result = freqtrade._get_trade_stake_amount() + assert(result == conf['stake_amount']) + + +def test_get_trade_stake_amount_no_stake_amount(default_conf, + ticker, + limit_buy_order, + fee, + mocker) -> None: + """ + Test get_trade_stake_amount() method + """ + patch_get_signal(mocker) + patch_RPCManager(mocker) + patch_coinmarketcap(mocker) + mocker.patch.multiple( + 'freqtrade.freqtradebot.exchange', + validate_pairs=MagicMock(), + get_ticker=ticker, + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5), + get_fee=fee, + ) + + conf = deepcopy(default_conf) + + freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) + + with pytest.raises(DependencyException, match=r'.*stake amount.*'): + freqtrade._get_trade_stake_amount() + + +def test_get_trade_stake_amount_unlimited_amount(default_conf, + ticker, + limit_buy_order, + fee, + mocker) -> None: + """ + Test get_trade_stake_amount() method + """ + patch_get_signal(mocker) + patch_RPCManager(mocker) + patch_coinmarketcap(mocker) + mocker.patch.multiple( + 'freqtrade.freqtradebot.exchange', + validate_pairs=MagicMock(), + get_ticker=ticker, + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + get_balance=MagicMock(return_value=default_conf['stake_amount']), + get_fee=fee, + ) + + conf = deepcopy(default_conf) + conf['stake_amount'] = 'unlimited' + conf['max_open_trades'] = 2 + + freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) + + # no open trades, order amount should be 'balance / max_open_trades' + result = freqtrade._get_trade_stake_amount() + assert(result == default_conf['stake_amount'] / conf['max_open_trades']) + + # create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)' + freqtrade.create_trade() + + result = freqtrade._get_trade_stake_amount() + assert(result == default_conf['stake_amount'] / (conf['max_open_trades'] - 1)) + + +def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, fee, mocker) -> None: + """ + Test create_trade() method + """ + patch_get_signal(mocker) + patch_RPCManager(mocker) + patch_coinmarketcap(mocker) + mocker.patch.multiple( + 'freqtrade.freqtradebot.exchange', + validate_pairs=MagicMock(), + get_ticker=ticker, + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5), + get_fee=fee, + ) + freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) + + with pytest.raises(DependencyException, match=r'.*stake amount.*'): + freqtrade.create_trade() + + def test_create_trade(default_conf, ticker, limit_buy_order, fee, mocker) -> None: """ Test create_trade() method @@ -281,27 +390,6 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, fee, assert rate * amount >= conf['stake_amount'] -def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, fee, mocker) -> None: - """ - Test create_trade() method - """ - patch_get_signal(mocker) - patch_RPCManager(mocker) - patch_coinmarketcap(mocker) - mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', - validate_pairs=MagicMock(), - get_ticker=ticker, - buy=MagicMock(return_value={'id': limit_buy_order['id']}), - get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5), - get_fee=fee, - ) - freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) - - with pytest.raises(DependencyException, match=r'.*stake amount.*'): - freqtrade.create_trade() - - def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, mocker) -> None: """ Test create_trade() method