Merge pull request #1746 from hroff-1902/json-defaults
Support for defaults in json schema
This commit is contained in:
commit
bf56e25404
@ -56,8 +56,10 @@
|
|||||||
},
|
},
|
||||||
"exchange": {
|
"exchange": {
|
||||||
"name": "bittrex",
|
"name": "bittrex",
|
||||||
|
"sandbox": false,
|
||||||
"key": "your_exchange_key",
|
"key": "your_exchange_key",
|
||||||
"secret": "your_exchange_secret",
|
"secret": "your_exchange_secret",
|
||||||
|
"password": "",
|
||||||
"ccxt_config": {"enableRateLimit": true},
|
"ccxt_config": {"enableRateLimit": true},
|
||||||
"ccxt_async_config": {
|
"ccxt_async_config": {
|
||||||
"enableRateLimit": false,
|
"enableRateLimit": false,
|
||||||
|
@ -194,7 +194,7 @@ optional arguments:
|
|||||||
### How to use **--refresh-pairs-cached** parameter?
|
### How to use **--refresh-pairs-cached** parameter?
|
||||||
|
|
||||||
The first time your run Backtesting, it will take the pairs you have
|
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
|
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.
|
`--refresh-pairs-cached` to force Backtesting to update the data it has.
|
||||||
|
@ -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.
|
| `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_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).
|
| `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.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` | '' | 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` | '' | 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.
|
||||||
| `exchange.pair_blacklist` | [] | List of currency the bot must avoid. Useful when using `--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)
|
| `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)
|
||||||
|
@ -116,7 +116,7 @@ Return a summary of your profit/loss and performance.
|
|||||||
|
|
||||||
### /forcebuy <pair>
|
### /forcebuy <pair>
|
||||||
|
|
||||||
> **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.
|
Note that for this to work, `forcebuy_enable` needs to be set to true.
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ from argparse import Namespace
|
|||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
from typing import Any, Dict, List, Optional
|
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 jsonschema.exceptions import ValidationError, best_match
|
||||||
|
|
||||||
from freqtrade import OperationalException, constants
|
from freqtrade import OperationalException, constants
|
||||||
@ -34,6 +34,27 @@ def set_loggers(log_level: int = 0) -> None:
|
|||||||
logging.getLogger('telegram').setLevel(logging.INFO)
|
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 Configuration(object):
|
||||||
"""
|
"""
|
||||||
Class to read and init the bot configuration
|
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
|
:return: Returns the config if valid, otherwise throw an exception
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
validate(conf, constants.CONF_SCHEMA, Draft4Validator)
|
ValidatorWithDefaults(constants.CONF_SCHEMA).validate(conf)
|
||||||
return conf
|
return conf
|
||||||
except ValidationError as exception:
|
except ValidationError as exception:
|
||||||
logger.critical(
|
logger.critical(
|
||||||
|
@ -173,10 +173,10 @@ CONF_SCHEMA = {
|
|||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
'name': {'type': 'string'},
|
'name': {'type': 'string'},
|
||||||
'sandbox': {'type': 'boolean'},
|
'sandbox': {'type': 'boolean', 'default': False},
|
||||||
'key': {'type': 'string'},
|
'key': {'type': 'string', 'default': ''},
|
||||||
'secret': {'type': 'string'},
|
'secret': {'type': 'string', 'default': ''},
|
||||||
'password': {'type': 'string'},
|
'password': {'type': 'string', 'default': ''},
|
||||||
'uid': {'type': 'string'},
|
'uid': {'type': 'string'},
|
||||||
'pair_whitelist': {
|
'pair_whitelist': {
|
||||||
'type': 'array',
|
'type': 'array',
|
||||||
@ -199,7 +199,7 @@ CONF_SCHEMA = {
|
|||||||
'ccxt_config': {'type': 'object'},
|
'ccxt_config': {'type': 'object'},
|
||||||
'ccxt_async_config': {'type': 'object'}
|
'ccxt_async_config': {'type': 'object'}
|
||||||
},
|
},
|
||||||
'required': ['name', 'key', 'secret', 'pair_whitelist']
|
'required': ['name', 'pair_whitelist']
|
||||||
},
|
},
|
||||||
'edge': {
|
'edge': {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
|
@ -53,7 +53,7 @@ class FreqtradeBot(object):
|
|||||||
|
|
||||||
self.rpc: RPCManager = RPCManager(self)
|
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.exchange = ExchangeResolver(exchange_name, self.config).exchange
|
||||||
|
|
||||||
self.wallets = Wallets(self.config, self.exchange)
|
self.wallets = Wallets(self.config, self.exchange)
|
||||||
|
@ -68,7 +68,7 @@ class Backtesting(object):
|
|||||||
self.config['dry_run'] = True
|
self.config['dry_run'] = True
|
||||||
self.strategylist: List[IStrategy] = []
|
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.exchange = ExchangeResolver(exchange_name, self.config).exchange
|
||||||
self.fee = self.exchange.get_fee()
|
self.fee = self.exchange.get_fee()
|
||||||
|
|
||||||
|
@ -18,6 +18,15 @@ from freqtrade.state import RunMode
|
|||||||
from freqtrade.tests.conftest import log_has
|
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:
|
def test_load_config_invalid_pair(default_conf) -> None:
|
||||||
default_conf['exchange']['pair_whitelist'].append('ETH-BTC')
|
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
|
default_conf['trailing_stop_positive_offset'] = 0.015
|
||||||
Configuration(Namespace())
|
Configuration(Namespace())
|
||||||
configuration._validate_config_consistency(default_conf)
|
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]
|
||||||
|
Loading…
Reference in New Issue
Block a user