diff --git a/config_full.json.example b/config_full.json.example index 20ba10c89..806b5f718 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -56,8 +56,10 @@ }, "exchange": { "name": "bittrex", + "sandbox": false, "key": "your_exchange_key", "secret": "your_exchange_secret", + "password": "", "ccxt_config": {"enableRateLimit": true}, "ccxt_async_config": { "enableRateLimit": false, diff --git a/docs/bot-usage.md b/docs/bot-usage.md index f5cd573ea..2b2fef640 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -194,7 +194,7 @@ optional arguments: ### How to use **--refresh-pairs-cached** parameter? The first time your run Backtesting, it will take the pairs you have -set in your config file and download data from Bittrex. +set in your config file and download data from the Exchange. If for any reason you want to update your data set, you use `--refresh-pairs-cached` to force Backtesting to update the data it has. diff --git a/docs/configuration.md b/docs/configuration.md index 0da14d9f6..dc623a728 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -40,10 +40,10 @@ Mandatory Parameters are marked as **Required**. | `ask_strategy.order_book_max` | 0 | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. | `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-the-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-the-strategy). -| `exchange.name` | bittrex | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). +| `exchange.name` | | **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.secret` | secret | API secret to use for the exchange. Only required when you are in production mode. +| `exchange.key` | '' | API key to use for the exchange. Only required when you are in production mode. +| `exchange.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_blacklist` | [] | List of currency the bot must avoid. Useful when using `--dynamic-whitelist` param. | `exchange.ccxt_config` | None | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 9d6877318..3947168c5 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -116,7 +116,7 @@ Return a summary of your profit/loss and performance. ### /forcebuy -> **BITTREX**: Buying ETH/BTC with limit `0.03400000` (`1.000000 ETH`, `225.290 USD`) +> **BITTREX:** Buying ETH/BTC with limit `0.03400000` (`1.000000 ETH`, `225.290 USD`) Note that for this to work, `forcebuy_enable` needs to be set to true. diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index db9f0c98e..6388a97ac 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -9,7 +9,7 @@ from argparse import Namespace from logging.handlers import RotatingFileHandler from typing import Any, Dict, List, Optional -from jsonschema import Draft4Validator, validate +from jsonschema import Draft4Validator, validators from jsonschema.exceptions import ValidationError, best_match from freqtrade import OperationalException, constants @@ -34,6 +34,27 @@ def set_loggers(log_level: int = 0) -> None: logging.getLogger('telegram').setLevel(logging.INFO) +def _extend_with_default(validator_class): + validate_properties = validator_class.VALIDATORS["properties"] + + def set_defaults(validator, properties, instance, schema): + for prop, subschema in properties.items(): + if "default" in subschema: + instance.setdefault(prop, subschema["default"]) + + for error in validate_properties( + validator, properties, instance, schema, + ): + yield error + + return validators.extend( + validator_class, {"properties": set_defaults}, + ) + + +ValidatorWithDefaults = _extend_with_default(Draft4Validator) + + class Configuration(object): """ Class to read and init the bot configuration @@ -331,7 +352,7 @@ class Configuration(object): :return: Returns the config if valid, otherwise throw an exception """ try: - validate(conf, constants.CONF_SCHEMA, Draft4Validator) + ValidatorWithDefaults(constants.CONF_SCHEMA).validate(conf) return conf except ValidationError as exception: logger.critical( diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 5243eeb4a..619508e73 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -173,10 +173,10 @@ CONF_SCHEMA = { 'type': 'object', 'properties': { 'name': {'type': 'string'}, - 'sandbox': {'type': 'boolean'}, - 'key': {'type': 'string'}, - 'secret': {'type': 'string'}, - 'password': {'type': 'string'}, + 'sandbox': {'type': 'boolean', 'default': False}, + 'key': {'type': 'string', 'default': ''}, + 'secret': {'type': 'string', 'default': ''}, + 'password': {'type': 'string', 'default': ''}, 'uid': {'type': 'string'}, 'pair_whitelist': { 'type': 'array', @@ -199,7 +199,7 @@ CONF_SCHEMA = { 'ccxt_config': {'type': 'object'}, 'ccxt_async_config': {'type': 'object'} }, - 'required': ['name', 'key', 'secret', 'pair_whitelist'] + 'required': ['name', 'pair_whitelist'] }, 'edge': { 'type': 'object', diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 2fe55336f..8b29d6d40 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -53,7 +53,7 @@ class FreqtradeBot(object): self.rpc: RPCManager = RPCManager(self) - exchange_name = self.config.get('exchange', {}).get('name', 'bittrex').title() + exchange_name = self.config.get('exchange', {}).get('name').title() self.exchange = ExchangeResolver(exchange_name, self.config).exchange self.wallets = Wallets(self.config, self.exchange) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index e7f06687d..eefbd4e04 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -68,7 +68,7 @@ class Backtesting(object): self.config['dry_run'] = True self.strategylist: List[IStrategy] = [] - exchange_name = self.config.get('exchange', {}).get('name', 'bittrex').title() + exchange_name = self.config.get('exchange', {}).get('name').title() self.exchange = ExchangeResolver(exchange_name, self.config).exchange self.fee = self.exchange.get_fee() diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index bcd0bd92c..09041dd3d 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -18,6 +18,15 @@ from freqtrade.state import RunMode from freqtrade.tests.conftest import log_has +@pytest.fixture(scope="function") +def all_conf(): + config_file = Path(__file__).parents[2] / "config_full.json.example" + print(config_file) + configuration = Configuration(Namespace()) + conf = configuration._load_config_file(str(config_file)) + return conf + + def test_load_config_invalid_pair(default_conf) -> None: default_conf['exchange']['pair_whitelist'].append('ETH-BTC') @@ -608,3 +617,59 @@ def test_validate_tsl(default_conf): default_conf['trailing_stop_positive_offset'] = 0.015 Configuration(Namespace()) configuration._validate_config_consistency(default_conf) + + +def test_load_config_default_exchange(all_conf) -> None: + """ + config['exchange'] subtree has required options in it + so it cannot be omitted in the config + """ + del all_conf['exchange'] + + assert 'exchange' not in all_conf + + with pytest.raises(ValidationError, + match=r'\'exchange\' is a required property'): + configuration = Configuration(Namespace()) + configuration._validate_config_schema(all_conf) + + +def test_load_config_default_exchange_name(all_conf) -> None: + """ + config['exchange']['name'] option is required + so it cannot be omitted in the config + """ + del all_conf['exchange']['name'] + + assert 'name' not in all_conf['exchange'] + + with pytest.raises(ValidationError, + match=r'\'name\' is a required property'): + configuration = Configuration(Namespace()) + configuration._validate_config_schema(all_conf) + + +@pytest.mark.parametrize("keys", [("exchange", "sandbox", False), + ("exchange", "key", ""), + ("exchange", "secret", ""), + ("exchange", "password", ""), + ]) +def test_load_config_default_subkeys(all_conf, keys) -> None: + """ + Test for parameters with default values in sub-paths + so they can be omitted in the config and the default value + should is added to the config. + """ + # Get first level key + key = keys[0] + # get second level key + subkey = keys[1] + + del all_conf[key][subkey] + + assert subkey not in all_conf[key] + + configuration = Configuration(Namespace()) + configuration._validate_config_schema(all_conf) + assert subkey in all_conf[key] + assert all_conf[key][subkey] == keys[2]