Add a Configuration class that generate the Bot config from Arguments

This commit is contained in:
Gerald Lonlas
2018-02-03 22:42:03 -08:00
parent 3b9e828fa4
commit 89e3729955
4 changed files with 266 additions and 286 deletions

View File

@@ -2,16 +2,18 @@
from datetime import datetime
from unittest.mock import MagicMock
from functools import reduce
import logging
import json
import arrow
import pytest
from jsonschema import validate
from telegram import Chat, Message, Update
from freqtrade.analyze import parse_ticker_dataframe
from freqtrade.analyze import Analyze
from freqtrade.constants import Constants
from freqtrade.strategy.strategy import Strategy
from freqtrade.misc import CONF_SCHEMA
logging.getLogger('').setLevel(logging.INFO)
def log_has(line, logs):
@@ -63,7 +65,7 @@ def default_conf():
},
"initial_state": "running"
}
validate(configuration, CONF_SCHEMA)
validate(configuration, Constants.CONF_SCHEMA)
return configuration
@@ -264,7 +266,7 @@ def ticker_history_without_bv():
@pytest.fixture
def result():
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
return parse_ticker_dataframe(json.load(data_file))
return Analyze.parse_ticker_dataframe(json.load(data_file))
@pytest.fixture

View File

@@ -1,281 +0,0 @@
# pragma pylint: disable=missing-docstring
from datetime import datetime
from unittest.mock import MagicMock
from functools import reduce
import json
import arrow
import pytest
from jsonschema import validate
from telegram import Chat, Message, Update
from freqtrade.analyze import parse_ticker_dataframe
from freqtrade.strategy.strategy import Strategy
from freqtrade.misc import CONF_SCHEMA
def log_has(line, logs):
# caplog mocker returns log as a tuple: ('freqtrade.analyze', logging.WARNING, 'foobar')
# and we want to match line against foobar in the tuple
return reduce(lambda a, b: a or b,
filter(lambda x: x[2] == line, logs),
False)
@pytest.fixture(scope="module")
def default_conf():
""" Returns validated configuration suitable for most tests """
configuration = {
"max_open_trades": 1,
"stake_currency": "BTC",
"stake_amount": 0.001,
"fiat_display_currency": "USD",
"ticker_interval": 5,
"dry_run": True,
"minimal_roi": {
"40": 0.0,
"30": 0.01,
"20": 0.02,
"0": 0.04
},
"stoploss": -0.10,
"unfilledtimeout": 600,
"bid_strategy": {
"ask_last_balance": 0.0
},
"exchange": {
"name": "bittrex",
"enabled": True,
"key": "key",
"secret": "secret",
"pair_whitelist": [
"BTC_ETH",
"BTC_TKN",
"BTC_TRST",
"BTC_SWT",
"BTC_BCC"
]
},
"telegram": {
"enabled": True,
"token": "token",
"chat_id": "0"
},
"initial_state": "running"
}
validate(configuration, CONF_SCHEMA)
return configuration
@pytest.fixture
def update():
_update = Update(0)
_update.message = Message(0, 0, datetime.utcnow(), Chat(0, 0))
return _update
@pytest.fixture
def ticker():
return MagicMock(return_value={
'bid': 0.00001098,
'ask': 0.00001099,
'last': 0.00001098,
})
@pytest.fixture
def ticker_sell_up():
return MagicMock(return_value={
'bid': 0.00001172,
'ask': 0.00001173,
'last': 0.00001172,
})
@pytest.fixture
def ticker_sell_down():
return MagicMock(return_value={
'bid': 0.00001044,
'ask': 0.00001043,
'last': 0.00001044,
})
@pytest.fixture
def health():
return MagicMock(return_value=[{
'Currency': 'BTC',
'IsActive': True,
'LastChecked': '2017-11-13T20:15:00.00',
'Notice': None
}, {
'Currency': 'ETH',
'IsActive': True,
'LastChecked': '2017-11-13T20:15:00.00',
'Notice': None
}, {
'Currency': 'TRST',
'IsActive': True,
'LastChecked': '2017-11-13T20:15:00.00',
'Notice': None
}, {
'Currency': 'SWT',
'IsActive': True,
'LastChecked': '2017-11-13T20:15:00.00',
'Notice': None
}, {
'Currency': 'BCC',
'IsActive': False,
'LastChecked': '2017-11-13T20:15:00.00',
'Notice': None
}])
@pytest.fixture
def limit_buy_order():
return {
'id': 'mocked_limit_buy',
'type': 'LIMIT_BUY',
'pair': 'mocked',
'opened': str(arrow.utcnow().datetime),
'rate': 0.00001099,
'amount': 90.99181073,
'remaining': 0.0,
'closed': str(arrow.utcnow().datetime),
}
@pytest.fixture
def limit_buy_order_old():
return {
'id': 'mocked_limit_buy_old',
'type': 'LIMIT_BUY',
'pair': 'BTC_ETH',
'opened': str(arrow.utcnow().shift(minutes=-601).datetime),
'rate': 0.00001099,
'amount': 90.99181073,
'remaining': 90.99181073,
}
@pytest.fixture
def limit_sell_order_old():
return {
'id': 'mocked_limit_sell_old',
'type': 'LIMIT_SELL',
'pair': 'BTC_ETH',
'opened': str(arrow.utcnow().shift(minutes=-601).datetime),
'rate': 0.00001099,
'amount': 90.99181073,
'remaining': 90.99181073,
}
@pytest.fixture
def limit_buy_order_old_partial():
return {
'id': 'mocked_limit_buy_old_partial',
'type': 'LIMIT_BUY',
'pair': 'BTC_ETH',
'opened': str(arrow.utcnow().shift(minutes=-601).datetime),
'rate': 0.00001099,
'amount': 90.99181073,
'remaining': 67.99181073,
}
@pytest.fixture
def limit_sell_order():
return {
'id': 'mocked_limit_sell',
'type': 'LIMIT_SELL',
'pair': 'mocked',
'opened': str(arrow.utcnow().datetime),
'rate': 0.00001173,
'amount': 90.99181073,
'remaining': 0.0,
'closed': str(arrow.utcnow().datetime),
}
@pytest.fixture
def ticker_history():
return [
{
"O": 8.794e-05,
"H": 8.948e-05,
"L": 8.794e-05,
"C": 8.88e-05,
"V": 991.09056638,
"T": "2017-11-26T08:50:00",
"BV": 0.0877869
},
{
"O": 8.88e-05,
"H": 8.942e-05,
"L": 8.88e-05,
"C": 8.893e-05,
"V": 658.77935965,
"T": "2017-11-26T08:55:00",
"BV": 0.05874751
},
{
"O": 8.891e-05,
"H": 8.893e-05,
"L": 8.875e-05,
"C": 8.877e-05,
"V": 7920.73570705,
"T": "2017-11-26T09:00:00",
"BV": 0.7039405
}
]
@pytest.fixture
def ticker_history_without_bv():
return [
{
"O": 8.794e-05,
"H": 8.948e-05,
"L": 8.794e-05,
"C": 8.88e-05,
"V": 991.09056638,
"T": "2017-11-26T08:50:00"
},
{
"O": 8.88e-05,
"H": 8.942e-05,
"L": 8.88e-05,
"C": 8.893e-05,
"V": 658.77935965,
"T": "2017-11-26T08:55:00"
},
{
"O": 8.891e-05,
"H": 8.893e-05,
"L": 8.875e-05,
"C": 8.877e-05,
"V": 7920.73570705,
"T": "2017-11-26T09:00:00"
}
]
@pytest.fixture
def result():
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
return parse_ticker_dataframe(json.load(data_file))
@pytest.fixture
def default_strategy():
strategy = Strategy()
strategy.init({'strategy': 'default_strategy'})
return strategy
# FIX:
# Create an fixture/function
# that inserts a trade of some type and open-status
# return the open-order-id
# See tests in rpc/main that could use this

View File

@@ -0,0 +1,157 @@
# pragma pylint: disable=protected-access, invalid-name, missing-docstring
"""
Unit test file for configuration.py
"""
import json
from copy import deepcopy
from unittest.mock import MagicMock
import pytest
from jsonschema import ValidationError
from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration
import freqtrade.tests.conftest as tt # test tools
def test_configuration_object() -> None:
"""
Test the Constants object has the mandatory Constants
:return: None
"""
assert hasattr(Configuration, '_load_config')
assert hasattr(Configuration, '_load_config_file')
assert hasattr(Configuration, '_validate_config')
assert hasattr(Configuration, 'show_info')
assert hasattr(Configuration, 'get_config')
def test_load_config_invalid_pair(default_conf, mocker) -> None:
"""
Test the configuration validator with an invalid PAIR format
:param default_conf: Configuration already read from a file (JSON format)
:return: None
"""
mocker.patch.multiple(
'freqtrade.configuration.Configuration',
_load_config=MagicMock(return_value=[])
)
conf = deepcopy(default_conf)
conf['exchange']['pair_whitelist'].append('BTC-ETH')
with pytest.raises(ValidationError, match=r'.*does not match.*'):
configuration = Configuration([])
configuration._validate_config(conf)
def test_load_config_missing_attributes(default_conf, mocker) -> None:
"""
Test the configuration validator with a missing attribute
:param default_conf: Configuration already read from a file (JSON format)
:return: None
"""
mocker.patch.multiple(
'freqtrade.configuration.Configuration',
_load_config=MagicMock(return_value=[])
)
conf = deepcopy(default_conf)
conf.pop('exchange')
with pytest.raises(ValidationError, match=r'.*\'exchange\' is a required property.*'):
configuration = Configuration([])
configuration._validate_config(conf)
def test_load_config_file(default_conf, mocker, caplog) -> None:
"""
Test _load_config_file() method
:return:
"""
mocker.patch.multiple(
'freqtrade.configuration.Configuration',
_load_config=MagicMock(return_value=[])
)
file_mock = mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf)
))
configuration = Configuration([])
validated_conf = configuration._load_config_file('somefile')
assert file_mock.call_count == 1
assert validated_conf.items() >= default_conf.items()
assert 'internals' in validated_conf
assert tt.log_has('Validating configuration ...', caplog.record_tuples)
def test_load_config(default_conf, mocker) -> None:
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf)
))
args = Arguments([], '').get_parsed_arg()
configuration = Configuration(args)
validated_conf = configuration._load_config()
assert 'strategy' in validated_conf
assert validated_conf['strategy'] == 'default_strategy'
assert 'dynamic_whitelist' not in validated_conf
assert 'dry_run_db' not in validated_conf
def test_load_config_with_params(default_conf, mocker) -> None:
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf)
))
args = [
'--dynamic-whitelist', '10',
'--strategy', 'test_strategy',
'--dry-run-db'
]
args = Arguments(args, '').get_parsed_arg()
configuration = Configuration(args)
validated_conf = configuration._load_config()
assert 'dynamic_whitelist' in validated_conf
assert validated_conf['dynamic_whitelist'] == 10
assert 'strategy' in validated_conf
assert validated_conf['strategy'] == 'test_strategy'
assert 'dry_run_db' in validated_conf
assert validated_conf['dry_run_db'] is True
def test_show_info(default_conf, mocker, caplog) -> None:
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf)
))
args = [
'--dynamic-whitelist', '10',
'--strategy', 'test_strategy',
'--dry-run-db'
]
args = Arguments(args, '').get_parsed_arg()
configuration = Configuration(args)
configuration.show_info()
assert tt.log_has(
'Using dynamically generated whitelist. (--dynamic-whitelist detected)',
caplog.record_tuples
)
assert tt.log_has(
'Dry_run will use the DB file: "tradesv3.dry_run.sqlite". '
'(--dry_run_db detected)',
caplog.record_tuples
)
# Test the Dry run condition
configuration.config.update({'dry_run': False})
configuration.show_info()
assert tt.log_has(
'Dry run is disabled. (--dry_run_db ignored)',
caplog.record_tuples
)