Merge branch 'develop' into trailing_only_offset
This commit is contained in:
		| @@ -23,10 +23,13 @@ install: | |||||||
| - pip install -r requirements-dev.txt | - pip install -r requirements-dev.txt | ||||||
| - pip install -e . | - pip install -e . | ||||||
| jobs: | jobs: | ||||||
|  |  | ||||||
|   include: |   include: | ||||||
|     - stage: tests |     - stage: tests | ||||||
|       script: |       script: | ||||||
|       - pytest --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/ |       - pytest --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/ | ||||||
|  |       # Allow failure for coveralls | ||||||
|  |       - coveralls || true   | ||||||
|       name: pytest |       name: pytest | ||||||
|     - script: |     - script: | ||||||
|       - cp config.json.example config.json |       - cp config.json.example config.json | ||||||
| @@ -47,9 +50,6 @@ jobs: | |||||||
|         - build_helpers/publish_docker.sh |         - build_helpers/publish_docker.sh | ||||||
|       name: "Build and test and push docker image" |       name: "Build and test and push docker image" | ||||||
|  |  | ||||||
| after_success: |  | ||||||
|   - coveralls |  | ||||||
|  |  | ||||||
| notifications: | notifications: | ||||||
|   slack: |   slack: | ||||||
|     secure: bKLXmOrx8e2aPZl7W8DA5BdPAXWGpI5UzST33oc1G/thegXcDVmHBTJrBs4sZak6bgAclQQrdZIsRd2eFYzHLalJEaw6pk7hoAw8SvLnZO0ZurWboz7qg2+aZZXfK4eKl/VUe4sM9M4e/qxjkK+yWG7Marg69c4v1ypF7ezUi1fPYILYw8u0paaiX0N5UX8XNlXy+PBlga2MxDjUY70MuajSZhPsY2pDUvYnMY1D/7XN3cFW0g+3O8zXjF0IF4q1Z/1ASQe+eYjKwPQacE+O8KDD+ZJYoTOFBAPllrtpO1jnOPFjNGf3JIbVMZw4bFjIL0mSQaiSUaUErbU3sFZ5Or79rF93XZ81V7uEZ55vD8KMfR2CB1cQJcZcj0v50BxLo0InkFqa0Y8Nra3sbpV4fV5Oe8pDmomPJrNFJnX6ULQhQ1gTCe0M5beKgVms5SITEpt4/Y0CmLUr6iHDT0CUiyMIRWAXdIgbGh1jfaWOMksybeRevlgDsIsNBjXmYI1Sw2ZZR2Eo2u4R6zyfyjOMLwYJ3vgq9IrACv2w5nmf0+oguMWHf6iWi2hiOqhlAN1W74+3HsYQcqnuM3LGOmuCnPprV1oGBqkPXjIFGpy21gNx4vHfO1noLUyJnMnlu2L7SSuN1CdLsnjJ1hVjpJjPfqB4nn8g12x87TqM1bOm+3Q= |     secure: bKLXmOrx8e2aPZl7W8DA5BdPAXWGpI5UzST33oc1G/thegXcDVmHBTJrBs4sZak6bgAclQQrdZIsRd2eFYzHLalJEaw6pk7hoAw8SvLnZO0ZurWboz7qg2+aZZXfK4eKl/VUe4sM9M4e/qxjkK+yWG7Marg69c4v1ypF7ezUi1fPYILYw8u0paaiX0N5UX8XNlXy+PBlga2MxDjUY70MuajSZhPsY2pDUvYnMY1D/7XN3cFW0g+3O8zXjF0IF4q1Z/1ASQe+eYjKwPQacE+O8KDD+ZJYoTOFBAPllrtpO1jnOPFjNGf3JIbVMZw4bFjIL0mSQaiSUaUErbU3sFZ5Or79rF93XZ81V7uEZ55vD8KMfR2CB1cQJcZcj0v50BxLo0InkFqa0Y8Nra3sbpV4fV5Oe8pDmomPJrNFJnX6ULQhQ1gTCe0M5beKgVms5SITEpt4/Y0CmLUr6iHDT0CUiyMIRWAXdIgbGh1jfaWOMksybeRevlgDsIsNBjXmYI1Sw2ZZR2Eo2u4R6zyfyjOMLwYJ3vgq9IrACv2w5nmf0+oguMWHf6iWi2hiOqhlAN1W74+3HsYQcqnuM3LGOmuCnPprV1oGBqkPXjIFGpy21gNx4vHfO1noLUyJnMnlu2L7SSuN1CdLsnjJ1hVjpJjPfqB4nn8g12x87TqM1bOm+3Q= | ||||||
|   | |||||||
| @@ -39,6 +39,7 @@ Mandatory Parameters are marked as **Required**. | |||||||
| | `order_types` | None | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-strategy). | | `order_types` | None | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-strategy). | ||||||
| | `order_time_in_force` | None | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-strategy). | | `order_time_in_force` | None | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-strategy). | ||||||
| | `exchange.name` | bittrex | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). | | `exchange.name` | bittrex | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). | ||||||
|  | | `exchange.sandbox` | false | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details. | ||||||
| | `exchange.key` | key | API key to use for the exchange. Only required when you are in production mode. | | `exchange.key` | key | API key to use for the exchange. Only required when you are in production mode. | ||||||
| | `exchange.secret` | secret | API secret to use for the exchange. Only required when you are in production mode. | | `exchange.secret` | secret | API secret to use for the exchange. Only required when you are in production mode. | ||||||
| | `exchange.pair_whitelist` | [] | List of currency to use by the bot. Can be overrided with `--dynamic-whitelist` param. | | `exchange.pair_whitelist` | [] | List of currency to use by the bot. Can be overrided with `--dynamic-whitelist` param. | ||||||
|   | |||||||
| @@ -36,7 +36,7 @@ Complementary Loss Rate (*L*) is defined as | |||||||
|  |  | ||||||
| or, which is the same, as | or, which is the same, as | ||||||
|  |  | ||||||
|     R = 1 – W |     L = 1 – W | ||||||
|  |  | ||||||
| ### Risk Reward Ratio | ### Risk Reward Ratio | ||||||
| Risk Reward Ratio (*R*) is a formula used to measure the expected gains of a given investment against the risk of loss. It is basically what you potentially win divided by what you potentially lose: | Risk Reward Ratio (*R*) is a formula used to measure the expected gains of a given investment against the risk of loss. It is basically what you potentially win divided by what you potentially lose: | ||||||
|   | |||||||
| @@ -44,6 +44,14 @@ CREATE TABLE trades ( | |||||||
| 	open_date DATETIME NOT NULL, | 	open_date DATETIME NOT NULL, | ||||||
| 	close_date DATETIME, | 	close_date DATETIME, | ||||||
| 	open_order_id VARCHAR, | 	open_order_id VARCHAR, | ||||||
|  | 	stop_loss FLOAT, | ||||||
|  | 	initial_stop_loss FLOAT, | ||||||
|  | 	stoploss_order_id VARCHAR, | ||||||
|  | 	stoploss_last_update DATETIME, | ||||||
|  | 	max_rate FLOAT, | ||||||
|  | 	sell_reason VARCHAR, | ||||||
|  | 	strategy VARCHAR, | ||||||
|  | 	ticker_interval INTEGER, | ||||||
| 	PRIMARY KEY (id), | 	PRIMARY KEY (id), | ||||||
| 	CHECK (is_open IN (0, 1)) | 	CHECK (is_open IN (0, 1)) | ||||||
| ); | ); | ||||||
| @@ -55,38 +63,45 @@ CREATE TABLE trades ( | |||||||
| SELECT * FROM trades; | SELECT * FROM trades; | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ## Fix trade still open after a /forcesell | ## Fix trade still open after a manual sell on the exchange | ||||||
|  |  | ||||||
|  | !!! Warning: | ||||||
|  |   Manually selling on the exchange should not be done by default, since the bot does not detect this and will try to sell anyway. | ||||||
|  |   /foresell <tradeid> should accomplish the same thing. | ||||||
|  |  | ||||||
|  | !!! Note: | ||||||
|  |   This should not be necessary after /forcesell, as forcesell orders are closed automatically by the bot on the next iteration. | ||||||
|  |  | ||||||
| ```sql | ```sql | ||||||
| UPDATE trades | UPDATE trades | ||||||
| SET is_open=0, close_date=<close_date>, close_rate=<close_rate>, close_profit=close_rate/open_rate-1   | SET is_open=0, close_date=<close_date>, close_rate=<close_rate>, close_profit=close_rate/open_rate-1, sell_reason=<sell_reason>   | ||||||
| WHERE id=<trade_ID_to_update>; | WHERE id=<trade_ID_to_update>; | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| **Example:** | ##### Example | ||||||
|  |  | ||||||
| ```sql | ```sql | ||||||
| UPDATE trades | UPDATE trades | ||||||
| SET is_open=0, close_date='2017-12-20 03:08:45.103418', close_rate=0.19638016, close_profit=0.0496   | SET is_open=0, close_date='2017-12-20 03:08:45.103418', close_rate=0.19638016, close_profit=0.0496, sell_reason='force_sell'   | ||||||
| WHERE id=31; | WHERE id=31; | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ## Insert manually a new trade | ## Insert manually a new trade | ||||||
|  |  | ||||||
| ```sql | ```sql | ||||||
| INSERT  | INSERT INTO trades (exchange, pair, is_open, fee_open, fee_close, open_rate, stake_amount, amount, open_date) | ||||||
| INTO trades (exchange, pair, is_open, fee_open, fee_close, open_rate, stake_amount, amount, open_date)  | VALUES ('bittrex', 'ETH/BTC', 1, 0.0025, 0.0025, <open_rate>, <stake_amount>, <amount>, '<datetime>') | ||||||
| VALUES ('BITTREX', 'BTC_<COIN>', 1, 0.0025, 0.0025, <open_rate>, <stake_amount>, <amount>, '<datetime>') |  | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| **Example:** | ##### Example: | ||||||
|  |  | ||||||
| ```sql | ```sql | ||||||
| INSERT INTO trades (exchange, pair, is_open, fee_open, fee_close, open_rate, stake_amount, amount, open_date) VALUES ('BITTREX', 'BTC_ETC', 1, 0.0025, 0.0025, 0.00258580, 0.002, 0.7715262081, '2017-11-28 12:44:24.000000') | INSERT INTO trades (exchange, pair, is_open, fee_open, fee_close, open_rate, stake_amount, amount, open_date) | ||||||
|  | VALUES ('bittrex', 'ETH/BTC', 1, 0.0025, 0.0025, 0.00258580, 0.002, 0.7715262081, '2017-11-28 12:44:24.000000') | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ## Fix wrong fees in the table | ## Fix wrong fees in the table | ||||||
| If your DB was created before  | If your DB was created before [PR#200](https://github.com/freqtrade/freqtrade/pull/200) was merged (before 12/23/17). | ||||||
| [PR#200](https://github.com/freqtrade/freqtrade/pull/200) was merged |  | ||||||
| (before 12/23/17). |  | ||||||
|  |  | ||||||
| ```sql | ```sql | ||||||
| UPDATE trades SET fee=0.0025 WHERE fee=0.005; | UPDATE trades SET fee=0.0025 WHERE fee=0.005; | ||||||
|   | |||||||
| @@ -298,7 +298,7 @@ class Exchange(object): | |||||||
|             'amount': amount, |             'amount': amount, | ||||||
|             "cost": amount * rate, |             "cost": amount * rate, | ||||||
|             'type': ordertype, |             'type': ordertype, | ||||||
|             'side': 'buy', |             'side': side, | ||||||
|             'remaining': amount, |             'remaining': amount, | ||||||
|             'datetime': arrow.utcnow().isoformat(), |             'datetime': arrow.utcnow().isoformat(), | ||||||
|             'status': "open", |             'status': "open", | ||||||
| @@ -352,7 +352,7 @@ class Exchange(object): | |||||||
|             return dry_order |             return dry_order | ||||||
|  |  | ||||||
|         params = self._params.copy() |         params = self._params.copy() | ||||||
|         if time_in_force != 'gtc': |         if time_in_force != 'gtc' and ordertype != 'market': | ||||||
|             params.update({'timeInForce': time_in_force}) |             params.update({'timeInForce': time_in_force}) | ||||||
|  |  | ||||||
|         return self.create_order(pair, ordertype, 'buy', amount, rate, params) |         return self.create_order(pair, ordertype, 'buy', amount, rate, params) | ||||||
| @@ -365,7 +365,7 @@ class Exchange(object): | |||||||
|             return dry_order |             return dry_order | ||||||
|  |  | ||||||
|         params = self._params.copy() |         params = self._params.copy() | ||||||
|         if time_in_force != 'gtc': |         if time_in_force != 'gtc' and ordertype != 'market': | ||||||
|             params.update({'timeInForce': time_in_force}) |             params.update({'timeInForce': time_in_force}) | ||||||
|  |  | ||||||
|         return self.create_order(pair, ordertype, 'sell', amount, rate, params) |         return self.create_order(pair, ordertype, 'sell', amount, rate, params) | ||||||
|   | |||||||
| @@ -61,6 +61,8 @@ class RPCManager(object): | |||||||
|         stake_currency = config['stake_currency'] |         stake_currency = config['stake_currency'] | ||||||
|         stake_amount = config['stake_amount'] |         stake_amount = config['stake_amount'] | ||||||
|         minimal_roi = config['minimal_roi'] |         minimal_roi = config['minimal_roi'] | ||||||
|  |         stoploss = config['stoploss'] | ||||||
|  |         trailing_stop = config['trailing_stop'] | ||||||
|         ticker_interval = config['ticker_interval'] |         ticker_interval = config['ticker_interval'] | ||||||
|         exchange_name = config['exchange']['name'] |         exchange_name = config['exchange']['name'] | ||||||
|         strategy_name = config.get('strategy', '') |         strategy_name = config.get('strategy', '') | ||||||
| @@ -69,6 +71,7 @@ class RPCManager(object): | |||||||
|             'status': f'*Exchange:* `{exchange_name}`\n' |             'status': f'*Exchange:* `{exchange_name}`\n' | ||||||
|                       f'*Stake per trade:* `{stake_amount} {stake_currency}`\n' |                       f'*Stake per trade:* `{stake_amount} {stake_currency}`\n' | ||||||
|                       f'*Minimum ROI:* `{minimal_roi}`\n' |                       f'*Minimum ROI:* `{minimal_roi}`\n' | ||||||
|  |                       f'*{"Trailing " if trailing_stop else ""}Stoploss:* `{stoploss}`\n' | ||||||
|                       f'*Ticker Interval:* `{ticker_interval}`\n' |                       f'*Ticker Interval:* `{ticker_interval}`\n' | ||||||
|                       f'*Strategy:* `{strategy_name}`' |                       f'*Strategy:* `{strategy_name}`' | ||||||
|         }) |         }) | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import copy | |||||||
| import logging | import logging | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
| from random import randint | from random import randint | ||||||
| from unittest.mock import Mock, MagicMock, PropertyMock | from unittest.mock import MagicMock, Mock, PropertyMock | ||||||
|  |  | ||||||
| import arrow | import arrow | ||||||
| import ccxt | import ccxt | ||||||
| @@ -12,11 +12,10 @@ import pytest | |||||||
| from pandas import DataFrame | from pandas import DataFrame | ||||||
|  |  | ||||||
| from freqtrade import DependencyException, OperationalException, TemporaryError | from freqtrade import DependencyException, OperationalException, TemporaryError | ||||||
| from freqtrade.exchange import Exchange, Kraken, Binance | from freqtrade.exchange import Binance, Exchange, Kraken | ||||||
| from freqtrade.exchange.exchange import API_RETRY_COUNT | from freqtrade.exchange.exchange import API_RETRY_COUNT | ||||||
| from freqtrade.tests.conftest import get_patched_exchange, log_has, log_has_re |  | ||||||
| from freqtrade.resolvers.exchange_resolver import ExchangeResolver | from freqtrade.resolvers.exchange_resolver import ExchangeResolver | ||||||
|  | from freqtrade.tests.conftest import get_patched_exchange, log_has, log_has_re | ||||||
|  |  | ||||||
| # Make sure to always keep one exchange here which is NOT subclassed!! | # Make sure to always keep one exchange here which is NOT subclassed!! | ||||||
| EXCHANGES = ['bittrex', 'binance', 'kraken', ] | EXCHANGES = ['bittrex', 'binance', 'kraken', ] | ||||||
| @@ -470,6 +469,9 @@ def test_dry_run_order(default_conf, mocker, side, exchange_name): | |||||||
|         pair='ETH/BTC', ordertype='limit', side=side, amount=1, rate=200) |         pair='ETH/BTC', ordertype='limit', side=side, amount=1, rate=200) | ||||||
|     assert 'id' in order |     assert 'id' in order | ||||||
|     assert f'dry_run_{side}_' in order["id"] |     assert f'dry_run_{side}_' in order["id"] | ||||||
|  |     assert order["side"] == side | ||||||
|  |     assert order["type"] == "limit" | ||||||
|  |     assert order["pair"] == "ETH/BTC" | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize("side", [ | @pytest.mark.parametrize("side", [ | ||||||
| @@ -588,11 +590,10 @@ def test_buy_prod(default_conf, mocker, exchange_name): | |||||||
|                      amount=1, rate=200, time_in_force=time_in_force) |                      amount=1, rate=200, time_in_force=time_in_force) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_buy_considers_time_in_force(default_conf, mocker): | @pytest.mark.parametrize("exchange_name", EXCHANGES) | ||||||
|  | def test_buy_considers_time_in_force(default_conf, mocker, exchange_name): | ||||||
|     api_mock = MagicMock() |     api_mock = MagicMock() | ||||||
|     order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) |     order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) | ||||||
|     order_type = 'market' |  | ||||||
|     time_in_force = 'ioc' |  | ||||||
|     api_mock.create_order = MagicMock(return_value={ |     api_mock.create_order = MagicMock(return_value={ | ||||||
|         'id': order_id, |         'id': order_id, | ||||||
|         'info': { |         'info': { | ||||||
| @@ -602,7 +603,27 @@ def test_buy_considers_time_in_force(default_conf, mocker): | |||||||
|     default_conf['dry_run'] = False |     default_conf['dry_run'] = False | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) |     mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) |     mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) | ||||||
|     exchange = get_patched_exchange(mocker, default_conf, api_mock) |     exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) | ||||||
|  |  | ||||||
|  |     order_type = 'limit' | ||||||
|  |     time_in_force = 'ioc' | ||||||
|  |  | ||||||
|  |     order = exchange.buy(pair='ETH/BTC', ordertype=order_type, | ||||||
|  |                          amount=1, rate=200, time_in_force=time_in_force) | ||||||
|  |  | ||||||
|  |     assert 'id' in order | ||||||
|  |     assert 'info' in order | ||||||
|  |     assert order['id'] == order_id | ||||||
|  |     assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' | ||||||
|  |     assert api_mock.create_order.call_args[0][1] == order_type | ||||||
|  |     assert api_mock.create_order.call_args[0][2] == 'buy' | ||||||
|  |     assert api_mock.create_order.call_args[0][3] == 1 | ||||||
|  |     assert api_mock.create_order.call_args[0][4] == 200 | ||||||
|  |     assert "timeInForce" in api_mock.create_order.call_args[0][5] | ||||||
|  |     assert api_mock.create_order.call_args[0][5]["timeInForce"] == time_in_force | ||||||
|  |  | ||||||
|  |     order_type = 'market' | ||||||
|  |     time_in_force = 'ioc' | ||||||
|  |  | ||||||
|     order = exchange.buy(pair='ETH/BTC', ordertype=order_type, |     order = exchange.buy(pair='ETH/BTC', ordertype=order_type, | ||||||
|                          amount=1, rate=200, time_in_force=time_in_force) |                          amount=1, rate=200, time_in_force=time_in_force) | ||||||
| @@ -615,68 +636,8 @@ def test_buy_considers_time_in_force(default_conf, mocker): | |||||||
|     assert api_mock.create_order.call_args[0][2] == 'buy' |     assert api_mock.create_order.call_args[0][2] == 'buy' | ||||||
|     assert api_mock.create_order.call_args[0][3] == 1 |     assert api_mock.create_order.call_args[0][3] == 1 | ||||||
|     assert api_mock.create_order.call_args[0][4] is None |     assert api_mock.create_order.call_args[0][4] is None | ||||||
|     assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'ioc'} |     # Market orders should not send timeInForce!! | ||||||
|  |     assert "timeInForce" not in api_mock.create_order.call_args[0][5] | ||||||
|  |  | ||||||
| def test_buy_kraken_trading_agreement(default_conf, mocker): |  | ||||||
|     api_mock = MagicMock() |  | ||||||
|     order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) |  | ||||||
|     order_type = 'market' |  | ||||||
|     time_in_force = 'ioc' |  | ||||||
|     api_mock.create_order = MagicMock(return_value={ |  | ||||||
|         'id': order_id, |  | ||||||
|         'info': { |  | ||||||
|             'foo': 'bar' |  | ||||||
|         } |  | ||||||
|     }) |  | ||||||
|     default_conf['dry_run'] = False |  | ||||||
|  |  | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) |  | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) |  | ||||||
|     exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") |  | ||||||
|  |  | ||||||
|     order = exchange.buy(pair='ETH/BTC', ordertype=order_type, |  | ||||||
|                          amount=1, rate=200, time_in_force=time_in_force) |  | ||||||
|  |  | ||||||
|     assert 'id' in order |  | ||||||
|     assert 'info' in order |  | ||||||
|     assert order['id'] == order_id |  | ||||||
|     assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' |  | ||||||
|     assert api_mock.create_order.call_args[0][1] == order_type |  | ||||||
|     assert api_mock.create_order.call_args[0][2] == 'buy' |  | ||||||
|     assert api_mock.create_order.call_args[0][3] == 1 |  | ||||||
|     assert api_mock.create_order.call_args[0][4] is None |  | ||||||
|     assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'ioc', |  | ||||||
|                                                      'trading_agreement': 'agree'} |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_sell_kraken_trading_agreement(default_conf, mocker): |  | ||||||
|     api_mock = MagicMock() |  | ||||||
|     order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6)) |  | ||||||
|     order_type = 'market' |  | ||||||
|     api_mock.create_order = MagicMock(return_value={ |  | ||||||
|         'id': order_id, |  | ||||||
|         'info': { |  | ||||||
|             'foo': 'bar' |  | ||||||
|         } |  | ||||||
|     }) |  | ||||||
|     default_conf['dry_run'] = False |  | ||||||
|  |  | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) |  | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) |  | ||||||
|     exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") |  | ||||||
|  |  | ||||||
|     order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) |  | ||||||
|  |  | ||||||
|     assert 'id' in order |  | ||||||
|     assert 'info' in order |  | ||||||
|     assert order['id'] == order_id |  | ||||||
|     assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' |  | ||||||
|     assert api_mock.create_order.call_args[0][1] == order_type |  | ||||||
|     assert api_mock.create_order.call_args[0][2] == 'sell' |  | ||||||
|     assert api_mock.create_order.call_args[0][3] == 1 |  | ||||||
|     assert api_mock.create_order.call_args[0][4] is None |  | ||||||
|     assert api_mock.create_order.call_args[0][5] == {'trading_agreement': 'agree'} |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_sell_dry_run(default_conf, mocker): | def test_sell_dry_run(default_conf, mocker): | ||||||
| @@ -747,6 +708,55 @@ def test_sell_prod(default_conf, mocker, exchange_name): | |||||||
|         exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) |         exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.parametrize("exchange_name", EXCHANGES) | ||||||
|  | def test_sell_considers_time_in_force(default_conf, mocker, exchange_name): | ||||||
|  |     api_mock = MagicMock() | ||||||
|  |     order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6)) | ||||||
|  |     api_mock.create_order = MagicMock(return_value={ | ||||||
|  |         'id': order_id, | ||||||
|  |         'info': { | ||||||
|  |             'foo': 'bar' | ||||||
|  |         } | ||||||
|  |     }) | ||||||
|  |     default_conf['dry_run'] = False | ||||||
|  |     mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) | ||||||
|  |     mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) | ||||||
|  |     exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) | ||||||
|  |  | ||||||
|  |     order_type = 'limit' | ||||||
|  |     time_in_force = 'ioc' | ||||||
|  |  | ||||||
|  |     order = exchange.sell(pair='ETH/BTC', ordertype=order_type, | ||||||
|  |                           amount=1, rate=200, time_in_force=time_in_force) | ||||||
|  |  | ||||||
|  |     assert 'id' in order | ||||||
|  |     assert 'info' in order | ||||||
|  |     assert order['id'] == order_id | ||||||
|  |     assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' | ||||||
|  |     assert api_mock.create_order.call_args[0][1] == order_type | ||||||
|  |     assert api_mock.create_order.call_args[0][2] == 'sell' | ||||||
|  |     assert api_mock.create_order.call_args[0][3] == 1 | ||||||
|  |     assert api_mock.create_order.call_args[0][4] == 200 | ||||||
|  |     assert "timeInForce" in api_mock.create_order.call_args[0][5] | ||||||
|  |     assert api_mock.create_order.call_args[0][5]["timeInForce"] == time_in_force | ||||||
|  |  | ||||||
|  |     order_type = 'market' | ||||||
|  |     time_in_force = 'ioc' | ||||||
|  |     order = exchange.sell(pair='ETH/BTC', ordertype=order_type, | ||||||
|  |                           amount=1, rate=200, time_in_force=time_in_force) | ||||||
|  |  | ||||||
|  |     assert 'id' in order | ||||||
|  |     assert 'info' in order | ||||||
|  |     assert order['id'] == order_id | ||||||
|  |     assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' | ||||||
|  |     assert api_mock.create_order.call_args[0][1] == order_type | ||||||
|  |     assert api_mock.create_order.call_args[0][2] == 'sell' | ||||||
|  |     assert api_mock.create_order.call_args[0][3] == 1 | ||||||
|  |     assert api_mock.create_order.call_args[0][4] is None | ||||||
|  |     # Market orders should not send timeInForce!! | ||||||
|  |     assert "timeInForce" not in api_mock.create_order.call_args[0][5] | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_get_balance_dry_run(default_conf, mocker): | def test_get_balance_dry_run(default_conf, mocker): | ||||||
|     default_conf['dry_run'] = True |     default_conf['dry_run'] = True | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										67
									
								
								freqtrade/tests/exchange/test_kraken.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								freqtrade/tests/exchange/test_kraken.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | # pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement | ||||||
|  | # pragma pylint: disable=protected-access | ||||||
|  | from random import randint | ||||||
|  | from unittest.mock import MagicMock | ||||||
|  |  | ||||||
|  | from freqtrade.tests.conftest import get_patched_exchange | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_buy_kraken_trading_agreement(default_conf, mocker): | ||||||
|  |     api_mock = MagicMock() | ||||||
|  |     order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) | ||||||
|  |     order_type = 'limit' | ||||||
|  |     time_in_force = 'ioc' | ||||||
|  |     api_mock.create_order = MagicMock(return_value={ | ||||||
|  |         'id': order_id, | ||||||
|  |         'info': { | ||||||
|  |             'foo': 'bar' | ||||||
|  |         } | ||||||
|  |     }) | ||||||
|  |     default_conf['dry_run'] = False | ||||||
|  |  | ||||||
|  |     mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) | ||||||
|  |     mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) | ||||||
|  |     exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") | ||||||
|  |  | ||||||
|  |     order = exchange.buy(pair='ETH/BTC', ordertype=order_type, | ||||||
|  |                          amount=1, rate=200, time_in_force=time_in_force) | ||||||
|  |  | ||||||
|  |     assert 'id' in order | ||||||
|  |     assert 'info' in order | ||||||
|  |     assert order['id'] == order_id | ||||||
|  |     assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' | ||||||
|  |     assert api_mock.create_order.call_args[0][1] == order_type | ||||||
|  |     assert api_mock.create_order.call_args[0][2] == 'buy' | ||||||
|  |     assert api_mock.create_order.call_args[0][3] == 1 | ||||||
|  |     assert api_mock.create_order.call_args[0][4] == 200 | ||||||
|  |     assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'ioc', | ||||||
|  |                                                      'trading_agreement': 'agree'} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_sell_kraken_trading_agreement(default_conf, mocker): | ||||||
|  |     api_mock = MagicMock() | ||||||
|  |     order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6)) | ||||||
|  |     order_type = 'market' | ||||||
|  |     api_mock.create_order = MagicMock(return_value={ | ||||||
|  |         'id': order_id, | ||||||
|  |         'info': { | ||||||
|  |             'foo': 'bar' | ||||||
|  |         } | ||||||
|  |     }) | ||||||
|  |     default_conf['dry_run'] = False | ||||||
|  |  | ||||||
|  |     mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) | ||||||
|  |     mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) | ||||||
|  |     exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") | ||||||
|  |  | ||||||
|  |     order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) | ||||||
|  |  | ||||||
|  |     assert 'id' in order | ||||||
|  |     assert 'info' in order | ||||||
|  |     assert order['id'] == order_id | ||||||
|  |     assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' | ||||||
|  |     assert api_mock.create_order.call_args[0][1] == order_type | ||||||
|  |     assert api_mock.create_order.call_args[0][2] == 'sell' | ||||||
|  |     assert api_mock.create_order.call_args[0][3] == 1 | ||||||
|  |     assert api_mock.create_order.call_args[0][4] is None | ||||||
|  |     assert api_mock.create_order.call_args[0][5] == {'trading_agreement': 'agree'} | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| # Include all requirements to run the bot. | # Include all requirements to run the bot. | ||||||
| -r requirements.txt | -r requirements.txt | ||||||
|  |  | ||||||
| plotly==3.6.1 | plotly==3.7.0 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| ccxt==1.18.345 | ccxt==1.18.357 | ||||||
| SQLAlchemy==1.3.0 | SQLAlchemy==1.3.1 | ||||||
| python-telegram-bot==11.1.0 | python-telegram-bot==11.1.0 | ||||||
| arrow==0.13.1 | arrow==0.13.1 | ||||||
| cachetools==3.1.0 | cachetools==3.1.0 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user