merged remote
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Dict, Optional
|
||||
from functools import reduce
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
@@ -34,7 +35,8 @@ def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
|
||||
:param config: Config to pass to the bot
|
||||
:return: None
|
||||
"""
|
||||
mocker.patch('freqtrade.fiat_convert.Market', {'price_usd': 12345.0})
|
||||
# mocker.patch('freqtrade.fiat_convert.Market', {'price_usd': 12345.0})
|
||||
patch_coinmarketcap(mocker, {'price_usd': 12345.0})
|
||||
mocker.patch('freqtrade.freqtradebot.Analyze', MagicMock())
|
||||
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
||||
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
|
||||
@@ -46,6 +48,27 @@ def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
|
||||
return FreqtradeBot(config, create_engine('sqlite://'))
|
||||
|
||||
|
||||
def patch_coinmarketcap(mocker, value: Optional[Dict[str, float]] = None) -> None:
|
||||
"""
|
||||
Mocker to coinmarketcap to speed up tests
|
||||
:param mocker: mocker to patch coinmarketcap class
|
||||
:return: None
|
||||
"""
|
||||
|
||||
tickermock = MagicMock(return_value={'price_usd': 12345.0})
|
||||
listmock = MagicMock(return_value={'data': [{'id': 1, 'name': 'Bitcoin', 'symbol': 'BTC',
|
||||
'website_slug': 'bitcoin'},
|
||||
{'id': 1027, 'name': 'Ethereum', 'symbol': 'ETH',
|
||||
'website_slug': 'ethereum'}
|
||||
]})
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.fiat_convert.Market',
|
||||
ticker=tickermock,
|
||||
listings=listmock,
|
||||
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def default_conf():
|
||||
""" Returns validated configuration suitable for most tests """
|
||||
@@ -54,7 +77,7 @@ def default_conf():
|
||||
"stake_currency": "BTC",
|
||||
"stake_amount": 0.001,
|
||||
"fiat_display_currency": "USD",
|
||||
"ticker_interval": 5,
|
||||
"ticker_interval": '5m',
|
||||
"dry_run": True,
|
||||
"minimal_roi": {
|
||||
"40": 0.0,
|
||||
@@ -73,11 +96,10 @@ def default_conf():
|
||||
"key": "key",
|
||||
"secret": "secret",
|
||||
"pair_whitelist": [
|
||||
"BTC_ETH",
|
||||
"BTC_TKN",
|
||||
"BTC_TRST",
|
||||
"BTC_SWT",
|
||||
"BTC_BCC"
|
||||
"ETH/BTC",
|
||||
"LTC/BTC",
|
||||
"XRP/BTC",
|
||||
"NEO/BTC"
|
||||
]
|
||||
},
|
||||
"telegram": {
|
||||
@@ -99,6 +121,11 @@ def update():
|
||||
return _update
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fee():
|
||||
return MagicMock(return_value=0.0025)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ticker():
|
||||
return MagicMock(return_value={
|
||||
@@ -127,46 +154,94 @@ def ticker_sell_down():
|
||||
|
||||
|
||||
@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
|
||||
}])
|
||||
def markets():
|
||||
return MagicMock(return_value=[
|
||||
{
|
||||
'id': 'ethbtc',
|
||||
'symbol': 'ETH/BTC',
|
||||
'base': 'ETH',
|
||||
'quote': 'BTC',
|
||||
'active': True,
|
||||
'precision': {
|
||||
'price': 8,
|
||||
'amount': 8,
|
||||
'cost': 8,
|
||||
},
|
||||
'lot': 0.00000001,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': 0.01,
|
||||
'max': 1000,
|
||||
},
|
||||
'price': 500000,
|
||||
'cost': 500000,
|
||||
},
|
||||
'info': '',
|
||||
},
|
||||
{
|
||||
'id': 'tknbtc',
|
||||
'symbol': 'TKN/BTC',
|
||||
'base': 'TKN',
|
||||
'quote': 'BTC',
|
||||
'active': True,
|
||||
'precision': {
|
||||
'price': 8,
|
||||
'amount': 8,
|
||||
'cost': 8,
|
||||
},
|
||||
'lot': 0.00000001,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': 0.01,
|
||||
'max': 1000,
|
||||
},
|
||||
'price': 500000,
|
||||
'cost': 500000,
|
||||
},
|
||||
'info': '',
|
||||
},
|
||||
{
|
||||
'id': 'blkbtc',
|
||||
'symbol': 'BLK/BTC',
|
||||
'base': 'BLK',
|
||||
'quote': 'BTC',
|
||||
'active': True,
|
||||
'precision': {
|
||||
'price': 8,
|
||||
'amount': 8,
|
||||
'cost': 8,
|
||||
},
|
||||
'lot': 0.00000001,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': 0.01,
|
||||
'max': 1000,
|
||||
},
|
||||
'price': 500000,
|
||||
'cost': 500000,
|
||||
},
|
||||
'info': '',
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def markets_empty():
|
||||
return MagicMock(return_value=[])
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def limit_buy_order():
|
||||
return {
|
||||
'id': 'mocked_limit_buy',
|
||||
'type': 'LIMIT_BUY',
|
||||
'type': 'limit',
|
||||
'side': 'buy',
|
||||
'pair': 'mocked',
|
||||
'opened': str(arrow.utcnow().datetime),
|
||||
'rate': 0.00001099,
|
||||
'datetime': arrow.utcnow().isoformat(),
|
||||
'price': 0.00001099,
|
||||
'amount': 90.99181073,
|
||||
'remaining': 0.0,
|
||||
'closed': str(arrow.utcnow().datetime),
|
||||
'status': 'closed'
|
||||
}
|
||||
|
||||
|
||||
@@ -174,12 +249,14 @@ def limit_buy_order():
|
||||
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,
|
||||
'type': 'limit',
|
||||
'side': 'buy',
|
||||
'pair': 'mocked',
|
||||
'datetime': str(arrow.utcnow().shift(minutes=-601).datetime),
|
||||
'price': 0.00001099,
|
||||
'amount': 90.99181073,
|
||||
'remaining': 90.99181073,
|
||||
'status': 'open'
|
||||
}
|
||||
|
||||
|
||||
@@ -187,12 +264,14 @@ def limit_buy_order_old():
|
||||
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,
|
||||
'type': 'limit',
|
||||
'side': 'sell',
|
||||
'pair': 'ETH/BTC',
|
||||
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
|
||||
'price': 0.00001099,
|
||||
'amount': 90.99181073,
|
||||
'remaining': 90.99181073,
|
||||
'status': 'open'
|
||||
}
|
||||
|
||||
|
||||
@@ -200,12 +279,14 @@ def limit_sell_order_old():
|
||||
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,
|
||||
'type': 'limit',
|
||||
'side': 'buy',
|
||||
'pair': 'ETH/BTC',
|
||||
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
|
||||
'price': 0.00001099,
|
||||
'amount': 90.99181073,
|
||||
'remaining': 67.99181073,
|
||||
'status': 'open'
|
||||
}
|
||||
|
||||
|
||||
@@ -213,86 +294,228 @@ def limit_buy_order_old_partial():
|
||||
def limit_sell_order():
|
||||
return {
|
||||
'id': 'mocked_limit_sell',
|
||||
'type': 'LIMIT_SELL',
|
||||
'type': 'limit',
|
||||
'side': 'sell',
|
||||
'pair': 'mocked',
|
||||
'opened': str(arrow.utcnow().datetime),
|
||||
'rate': 0.00001173,
|
||||
'datetime': arrow.utcnow().isoformat(),
|
||||
'price': 0.00001173,
|
||||
'amount': 90.99181073,
|
||||
'remaining': 0.0,
|
||||
'closed': str(arrow.utcnow().datetime),
|
||||
'status': 'closed'
|
||||
}
|
||||
|
||||
|
||||
@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
|
||||
}
|
||||
[
|
||||
1511686200000, # unix timestamp ms
|
||||
8.794e-05, # open
|
||||
8.948e-05, # high
|
||||
8.794e-05, # low
|
||||
8.88e-05, # close
|
||||
0.0877869, # volume (in quote currency)
|
||||
],
|
||||
[
|
||||
1511686500000,
|
||||
8.88e-05,
|
||||
8.942e-05,
|
||||
8.88e-05,
|
||||
8.893e-05,
|
||||
0.05874751,
|
||||
],
|
||||
[
|
||||
1511686800000,
|
||||
8.891e-05,
|
||||
8.893e-05,
|
||||
8.875e-05,
|
||||
8.877e-05,
|
||||
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"
|
||||
def tickers():
|
||||
return MagicMock(return_value={
|
||||
'ETH/BTC': {
|
||||
'symbol': 'ETH/BTC',
|
||||
'timestamp': 1522014806207,
|
||||
'datetime': '2018-03-25T21:53:26.207Z',
|
||||
'high': 0.061697,
|
||||
'low': 0.060531,
|
||||
'bid': 0.061588,
|
||||
'bidVolume': 3.321,
|
||||
'ask': 0.061655,
|
||||
'askVolume': 0.212,
|
||||
'vwap': 0.06105296,
|
||||
'open': 0.060809,
|
||||
'close': 0.060761,
|
||||
'first': None,
|
||||
'last': 0.061588,
|
||||
'change': 1.281,
|
||||
'percentage': None,
|
||||
'average': None,
|
||||
'baseVolume': 111649.001,
|
||||
'quoteVolume': 6816.50176926,
|
||||
'info': {}
|
||||
},
|
||||
{
|
||||
"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"
|
||||
'TKN/BTC': {
|
||||
'symbol': 'TKN/BTC',
|
||||
'timestamp': 1522014806169,
|
||||
'datetime': '2018-03-25T21:53:26.169Z',
|
||||
'high': 0.01885,
|
||||
'low': 0.018497,
|
||||
'bid': 0.018799,
|
||||
'bidVolume': 8.38,
|
||||
'ask': 0.018802,
|
||||
'askVolume': 15.0,
|
||||
'vwap': 0.01869197,
|
||||
'open': 0.018585,
|
||||
'close': 0.018573,
|
||||
'baseVolume': 81058.66,
|
||||
'quoteVolume': 2247.48374509,
|
||||
},
|
||||
{
|
||||
"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"
|
||||
'BLK/BTC': {
|
||||
'symbol': 'BLK/BTC',
|
||||
'timestamp': 1522014806072,
|
||||
'datetime': '2018-03-25T21:53:26.720Z',
|
||||
'high': 0.007745,
|
||||
'low': 0.007512,
|
||||
'bid': 0.007729,
|
||||
'bidVolume': 0.01,
|
||||
'ask': 0.007743,
|
||||
'askVolume': 21.37,
|
||||
'vwap': 0.00761466,
|
||||
'open': 0.007653,
|
||||
'close': 0.007652,
|
||||
'first': None,
|
||||
'last': 0.007743,
|
||||
'change': 1.176,
|
||||
'percentage': None,
|
||||
'average': None,
|
||||
'baseVolume': 295152.26,
|
||||
'quoteVolume': 1515.14631229,
|
||||
'info': {}
|
||||
},
|
||||
'LTC/BTC': {
|
||||
'symbol': 'LTC/BTC',
|
||||
'timestamp': 1523787258992,
|
||||
'datetime': '2018-04-15T10:14:19.992Z',
|
||||
'high': 0.015978,
|
||||
'low': 0.0157,
|
||||
'bid': 0.015954,
|
||||
'bidVolume': 12.83,
|
||||
'ask': 0.015957,
|
||||
'askVolume': 0.49,
|
||||
'vwap': 0.01581636,
|
||||
'open': 0.015823,
|
||||
'close': 0.01582,
|
||||
'first': None,
|
||||
'last': 0.015951,
|
||||
'change': 0.809,
|
||||
'percentage': None,
|
||||
'average': None,
|
||||
'baseVolume': 88620.68,
|
||||
'quoteVolume': 1401.65697943,
|
||||
'info': {}
|
||||
},
|
||||
'ETH/USDT': {
|
||||
'symbol': 'ETH/USDT',
|
||||
'timestamp': 1522014804118,
|
||||
'datetime': '2018-03-25T21:53:24.118Z',
|
||||
'high': 530.88,
|
||||
'low': 512.0,
|
||||
'bid': 529.73,
|
||||
'bidVolume': 0.2,
|
||||
'ask': 530.21,
|
||||
'askVolume': 0.2464,
|
||||
'vwap': 521.02438405,
|
||||
'open': 527.27,
|
||||
'close': 528.42,
|
||||
'first': None,
|
||||
'last': 530.21,
|
||||
'change': 0.558,
|
||||
'percentage': None,
|
||||
'average': None,
|
||||
'baseVolume': 72300.0659,
|
||||
'quoteVolume': 37670097.3022171,
|
||||
'info': {}
|
||||
},
|
||||
'TKN/USDT': {
|
||||
'symbol': 'TKN/USDT',
|
||||
'timestamp': 1522014806198,
|
||||
'datetime': '2018-03-25T21:53:26.198Z',
|
||||
'high': 8718.0,
|
||||
'low': 8365.77,
|
||||
'bid': 8603.64,
|
||||
'bidVolume': 0.15846,
|
||||
'ask': 8603.67,
|
||||
'askVolume': 0.069147,
|
||||
'vwap': 8536.35621697,
|
||||
'open': 8680.0,
|
||||
'close': 8680.0,
|
||||
'first': None,
|
||||
'last': 8603.67,
|
||||
'change': -0.879,
|
||||
'percentage': None,
|
||||
'average': None,
|
||||
'baseVolume': 30414.604298,
|
||||
'quoteVolume': 259629896.48584127,
|
||||
'info': {}
|
||||
},
|
||||
'BLK/USDT': {
|
||||
'symbol': 'BLK/USDT',
|
||||
'timestamp': 1522014806145,
|
||||
'datetime': '2018-03-25T21:53:26.145Z',
|
||||
'high': 66.95,
|
||||
'low': 63.38,
|
||||
'bid': 66.473,
|
||||
'bidVolume': 4.968,
|
||||
'ask': 66.54,
|
||||
'askVolume': 2.704,
|
||||
'vwap': 65.0526901,
|
||||
'open': 66.43,
|
||||
'close': 66.383,
|
||||
'first': None,
|
||||
'last': 66.5,
|
||||
'change': 0.105,
|
||||
'percentage': None,
|
||||
'average': None,
|
||||
'baseVolume': 294106.204,
|
||||
'quoteVolume': 19132399.743954,
|
||||
'info': {}
|
||||
},
|
||||
'LTC/USDT': {
|
||||
'symbol': 'LTC/USDT',
|
||||
'timestamp': 1523787257812,
|
||||
'datetime': '2018-04-15T10:14:18.812Z',
|
||||
'high': 129.94,
|
||||
'low': 124.0,
|
||||
'bid': 129.28,
|
||||
'bidVolume': 0.03201,
|
||||
'ask': 129.52,
|
||||
'askVolume': 0.14529,
|
||||
'vwap': 126.92838682,
|
||||
'open': 127.0,
|
||||
'close': 127.1,
|
||||
'first': None,
|
||||
'last': 129.28,
|
||||
'change': 1.795,
|
||||
'percentage': None,
|
||||
'average': None,
|
||||
'baseVolume': 59698.79897,
|
||||
'quoteVolume': 29132399.743954,
|
||||
'info': {}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
# FIX: Perhaps change result fixture to use BTC_UNITEST instead?
|
||||
@pytest.fixture
|
||||
def result():
|
||||
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
|
||||
with open('freqtrade/tests/testdata/UNITTEST_BTC-1m.json') as data_file:
|
||||
return Analyze.parse_ticker_dataframe(json.load(data_file))
|
||||
|
||||
|
||||
# FIX:
|
||||
# Create an fixture/function
|
||||
# that inserts a trade of some type and open-status
|
||||
@@ -300,132 +523,88 @@ def result():
|
||||
# See tests in rpc/main that could use this
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def trades_for_order():
|
||||
return [{'info': {'id': 34567,
|
||||
'orderId': 123456,
|
||||
'price': '0.24544100',
|
||||
'qty': '8.00000000',
|
||||
'commission': '0.00800000',
|
||||
'commissionAsset': 'LTC',
|
||||
'time': 1521663363189,
|
||||
'isBuyer': True,
|
||||
'isMaker': False,
|
||||
'isBestMatch': True},
|
||||
'timestamp': 1521663363189,
|
||||
'datetime': '2018-03-21T20:16:03.189Z',
|
||||
'symbol': 'LTC/ETH',
|
||||
'id': '34567',
|
||||
'order': '123456',
|
||||
'type': None,
|
||||
'side': 'buy',
|
||||
'price': 0.245441,
|
||||
'cost': 1.963528,
|
||||
'amount': 8.0,
|
||||
'fee': {'cost': 0.008, 'currency': 'LTC'}}]
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def trades_for_order2():
|
||||
return [{'info': {'id': 34567,
|
||||
'orderId': 123456,
|
||||
'price': '0.24544100',
|
||||
'qty': '8.00000000',
|
||||
'commission': '0.00800000',
|
||||
'commissionAsset': 'LTC',
|
||||
'time': 1521663363189,
|
||||
'isBuyer': True,
|
||||
'isMaker': False,
|
||||
'isBestMatch': True},
|
||||
'timestamp': 1521663363189,
|
||||
'datetime': '2018-03-21T20:16:03.189Z',
|
||||
'symbol': 'LTC/ETH',
|
||||
'id': '34567',
|
||||
'order': '123456',
|
||||
'type': None,
|
||||
'side': 'buy',
|
||||
'price': 0.245441,
|
||||
'cost': 1.963528,
|
||||
'amount': 4.0,
|
||||
'fee': {'cost': 0.004, 'currency': 'LTC'}},
|
||||
{'info': {'id': 34567,
|
||||
'orderId': 123456,
|
||||
'price': '0.24544100',
|
||||
'qty': '8.00000000',
|
||||
'commission': '0.00800000',
|
||||
'commissionAsset': 'LTC',
|
||||
'time': 1521663363189,
|
||||
'isBuyer': True,
|
||||
'isMaker': False,
|
||||
'isBestMatch': True},
|
||||
'timestamp': 1521663363189,
|
||||
'datetime': '2018-03-21T20:16:03.189Z',
|
||||
'symbol': 'LTC/ETH',
|
||||
'id': '34567',
|
||||
'order': '123456',
|
||||
'type': None,
|
||||
'side': 'buy',
|
||||
'price': 0.245441,
|
||||
'cost': 1.963528,
|
||||
'amount': 4.0,
|
||||
'fee': {'cost': 0.004, 'currency': 'LTC'}}]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def get_market_summaries_data():
|
||||
"""
|
||||
This fixture is a real result from exchange.get_market_summaries() but reduced to only
|
||||
8 entries. 4 BTC, 4 USTD
|
||||
:return: JSON market summaries
|
||||
"""
|
||||
return [
|
||||
{
|
||||
'Ask': 1.316e-05,
|
||||
'BaseVolume': 5.72599471,
|
||||
'Bid': 1.3e-05,
|
||||
'Created': '2014-04-14T00:00:00',
|
||||
'High': 1.414e-05,
|
||||
'Last': 1.298e-05,
|
||||
'Low': 1.282e-05,
|
||||
'MarketName': 'BTC-XWC',
|
||||
'OpenBuyOrders': 2000,
|
||||
'OpenSellOrders': 1484,
|
||||
'PrevDay': 1.376e-05,
|
||||
'TimeStamp': '2018-02-05T01:32:40.493',
|
||||
'Volume': 424041.21418375
|
||||
},
|
||||
{
|
||||
'Ask': 0.00627051,
|
||||
'BaseVolume': 93.23302388,
|
||||
'Bid': 0.00618192,
|
||||
'Created': '2016-10-20T04:48:30.387',
|
||||
'High': 0.00669897,
|
||||
'Last': 0.00618192,
|
||||
'Low': 0.006,
|
||||
'MarketName': 'BTC-XZC',
|
||||
'OpenBuyOrders': 343,
|
||||
'OpenSellOrders': 2037,
|
||||
'PrevDay': 0.00668229,
|
||||
'TimeStamp': '2018-02-05T01:32:43.383',
|
||||
'Volume': 14863.60730702
|
||||
},
|
||||
{
|
||||
'Ask': 0.01137247,
|
||||
'BaseVolume': 383.55922657,
|
||||
'Bid': 0.01136006,
|
||||
'Created': '2016-11-15T20:29:59.73',
|
||||
'High': 0.012,
|
||||
'Last': 0.01137247,
|
||||
'Low': 0.01119883,
|
||||
'MarketName': 'BTC-ZCL',
|
||||
'OpenBuyOrders': 1332,
|
||||
'OpenSellOrders': 5317,
|
||||
'PrevDay': 0.01179603,
|
||||
'TimeStamp': '2018-02-05T01:32:42.773',
|
||||
'Volume': 33308.07358285
|
||||
},
|
||||
{
|
||||
'Ask': 0.04155821,
|
||||
'BaseVolume': 274.75369074,
|
||||
'Bid': 0.04130002,
|
||||
'Created': '2016-10-28T17:13:10.833',
|
||||
'High': 0.04354429,
|
||||
'Last': 0.041585,
|
||||
'Low': 0.0413,
|
||||
'MarketName': 'BTC-ZEC',
|
||||
'OpenBuyOrders': 863,
|
||||
'OpenSellOrders': 5579,
|
||||
'PrevDay': 0.0429,
|
||||
'TimeStamp': '2018-02-05T01:32:43.21',
|
||||
'Volume': 6479.84033259
|
||||
},
|
||||
{
|
||||
'Ask': 210.99999999,
|
||||
'BaseVolume': 615132.70989532,
|
||||
'Bid': 210.05503736,
|
||||
'Created': '2017-07-21T01:08:49.397',
|
||||
'High': 257.396,
|
||||
'Last': 211.0,
|
||||
'Low': 209.05333589,
|
||||
'MarketName': 'USDT-XMR',
|
||||
'OpenBuyOrders': 180,
|
||||
'OpenSellOrders': 1203,
|
||||
'PrevDay': 247.93528899,
|
||||
'TimeStamp': '2018-02-05T01:32:43.117',
|
||||
'Volume': 2688.17410793
|
||||
},
|
||||
{
|
||||
'Ask': 0.79589979,
|
||||
'BaseVolume': 9349557.01853031,
|
||||
'Bid': 0.789226,
|
||||
'Created': '2017-07-14T17:10:10.737',
|
||||
'High': 0.977,
|
||||
'Last': 0.79589979,
|
||||
'Low': 0.781,
|
||||
'MarketName': 'USDT-XRP',
|
||||
'OpenBuyOrders': 1075,
|
||||
'OpenSellOrders': 6508,
|
||||
'PrevDay': 0.93300218,
|
||||
'TimeStamp': '2018-02-05T01:32:42.383',
|
||||
'Volume': 10801663.00788851
|
||||
},
|
||||
{
|
||||
'Ask': 0.05154982,
|
||||
'BaseVolume': 2311087.71232136,
|
||||
'Bid': 0.05040107,
|
||||
'Created': '2017-12-29T19:29:18.357',
|
||||
'High': 0.06668561,
|
||||
'Last': 0.0508,
|
||||
'Low': 0.05006731,
|
||||
'MarketName': 'USDT-XVG',
|
||||
'OpenBuyOrders': 655,
|
||||
'OpenSellOrders': 5544,
|
||||
'PrevDay': 0.0627,
|
||||
'TimeStamp': '2018-02-05T01:32:41.507',
|
||||
'Volume': 40031424.2152716
|
||||
},
|
||||
{
|
||||
'Ask': 332.65500022,
|
||||
'BaseVolume': 562911.87455665,
|
||||
'Bid': 330.00000001,
|
||||
'Created': '2017-07-14T17:10:10.673',
|
||||
'High': 401.59999999,
|
||||
'Last': 332.65500019,
|
||||
'Low': 330.0,
|
||||
'MarketName': 'USDT-ZEC',
|
||||
'OpenBuyOrders': 161,
|
||||
'OpenSellOrders': 1731,
|
||||
'PrevDay': 391.42,
|
||||
'TimeStamp': '2018-02-05T01:32:42.947',
|
||||
'Volume': 1571.09647946
|
||||
}
|
||||
]
|
||||
def buy_order_fee():
|
||||
return {
|
||||
'id': 'mocked_limit_buy_old',
|
||||
'type': 'limit',
|
||||
'side': 'buy',
|
||||
'pair': 'mocked',
|
||||
'datetime': str(arrow.utcnow().shift(minutes=-601).datetime),
|
||||
'price': 0.245441,
|
||||
'amount': 8.0,
|
||||
'remaining': 90.99181073,
|
||||
'status': 'closed',
|
||||
'fee': None
|
||||
}
|
||||
|
@@ -1,16 +1,18 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement
|
||||
# pragma pylint: disable=protected-access
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
from random import randint
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
import ccxt
|
||||
import pytest
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
import freqtrade.exchange as exchange
|
||||
from freqtrade import OperationalException
|
||||
from freqtrade.exchange import init, validate_pairs, buy, sell, get_balance, get_balances, \
|
||||
get_ticker, get_ticker_history, cancel_order, get_name, get_fee
|
||||
from freqtrade import OperationalException, DependencyException, TemporaryError
|
||||
from freqtrade.exchange import (init, validate_pairs, buy, sell, get_balance, get_balances,
|
||||
get_ticker, get_ticker_history, cancel_order, get_name, get_fee,
|
||||
get_id, get_pair_detail_url, get_amount_lots)
|
||||
from freqtrade.tests.conftest import log_has
|
||||
|
||||
API_INIT = False
|
||||
@@ -42,9 +44,12 @@ def test_init_exception(default_conf):
|
||||
|
||||
def test_validate_pairs(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
api_mock.get_markets = MagicMock(return_value=[
|
||||
'BTC_ETH', 'BTC_TKN', 'BTC_TRST', 'BTC_SWT', 'BTC_BCC',
|
||||
])
|
||||
api_mock.load_markets = MagicMock(return_value={
|
||||
'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': ''
|
||||
})
|
||||
id_mock = PropertyMock(return_value='test_exchange')
|
||||
type(api_mock).id = id_mock
|
||||
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
||||
@@ -52,7 +57,7 @@ def test_validate_pairs(default_conf, mocker):
|
||||
|
||||
def test_validate_pairs_not_available(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
api_mock.get_markets = MagicMock(return_value=[])
|
||||
api_mock.load_markets = MagicMock(return_value={})
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||
with pytest.raises(OperationalException, match=r'not available'):
|
||||
@@ -61,63 +66,148 @@ def test_validate_pairs_not_available(default_conf, mocker):
|
||||
|
||||
def test_validate_pairs_not_compatible(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
api_mock.get_markets = MagicMock(
|
||||
return_value=['BTC_ETH', 'BTC_TKN', 'BTC_TRST', 'BTC_SWT'])
|
||||
default_conf['stake_currency'] = 'ETH'
|
||||
api_mock.load_markets = MagicMock(return_value={
|
||||
'ETH/BTC': '', 'TKN/BTC': '', 'TRST/BTC': '', 'SWT/BTC': '', 'BCC/BTC': ''
|
||||
})
|
||||
conf = deepcopy(default_conf)
|
||||
conf['stake_currency'] = 'ETH'
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', conf)
|
||||
with pytest.raises(OperationalException, match=r'not compatible'):
|
||||
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
||||
validate_pairs(conf['exchange']['pair_whitelist'])
|
||||
|
||||
|
||||
def test_validate_pairs_exception(default_conf, mocker, caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
api_mock = MagicMock()
|
||||
api_mock.get_markets = MagicMock(side_effect=RequestException())
|
||||
api_mock.name = 'Binance'
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||
|
||||
# with pytest.raises(RequestException, match=r'Unable to validate pairs'):
|
||||
api_mock.load_markets = MagicMock(return_value={})
|
||||
with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available at Binance'):
|
||||
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
||||
|
||||
api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError())
|
||||
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
||||
assert log_has('Unable to validate pairs (assuming they are correct). Reason: ',
|
||||
caplog.record_tuples)
|
||||
|
||||
|
||||
def test_validate_pairs_stake_exception(default_conf, mocker, caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
conf = deepcopy(default_conf)
|
||||
conf['stake_currency'] = 'ETH'
|
||||
api_mock = MagicMock()
|
||||
api_mock.name = 'binance'
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', conf)
|
||||
|
||||
with pytest.raises(
|
||||
OperationalException,
|
||||
match=r'Pair ETH/BTC not compatible with stake_currency: ETH'
|
||||
):
|
||||
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
||||
|
||||
|
||||
def test_buy_dry_run(default_conf, mocker):
|
||||
default_conf['dry_run'] = True
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||
|
||||
assert 'dry_run_buy_' in buy(pair='BTC_ETH', rate=200, amount=1)
|
||||
order = buy(pair='ETH/BTC', rate=200, amount=1)
|
||||
assert 'id' in order
|
||||
assert 'dry_run_buy_' in order['id']
|
||||
|
||||
|
||||
def test_buy_prod(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
api_mock.buy = MagicMock(
|
||||
return_value='dry_run_buy_{}'.format(randint(0, 10**6)))
|
||||
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
||||
api_mock.create_limit_buy_order = MagicMock(return_value={
|
||||
'id': order_id,
|
||||
'info': {
|
||||
'foo': 'bar'
|
||||
}
|
||||
})
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
|
||||
default_conf['dry_run'] = False
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||
|
||||
assert 'dry_run_buy_' in buy(pair='BTC_ETH', rate=200, amount=1)
|
||||
order = buy(pair='ETH/BTC', rate=200, amount=1)
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
assert order['id'] == order_id
|
||||
|
||||
# test exception handling
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.InsufficientFunds)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
buy(pair='ETH/BTC', rate=200, amount=1)
|
||||
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
buy(pair='ETH/BTC', rate=200, amount=1)
|
||||
|
||||
with pytest.raises(TemporaryError):
|
||||
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
buy(pair='ETH/BTC', rate=200, amount=1)
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.BaseError)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
buy(pair='ETH/BTC', rate=200, amount=1)
|
||||
|
||||
|
||||
def test_sell_dry_run(default_conf, mocker):
|
||||
default_conf['dry_run'] = True
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||
|
||||
assert 'dry_run_sell_' in sell(pair='BTC_ETH', rate=200, amount=1)
|
||||
order = sell(pair='ETH/BTC', rate=200, amount=1)
|
||||
assert 'id' in order
|
||||
assert 'dry_run_sell_' in order['id']
|
||||
|
||||
|
||||
def test_sell_prod(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
api_mock.sell = MagicMock(
|
||||
return_value='dry_run_sell_{}'.format(randint(0, 10**6)))
|
||||
order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6))
|
||||
api_mock.create_limit_sell_order = MagicMock(return_value={
|
||||
'id': order_id,
|
||||
'info': {
|
||||
'foo': 'bar'
|
||||
}
|
||||
})
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
|
||||
default_conf['dry_run'] = False
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||
|
||||
assert 'dry_run_sell_' in sell(pair='BTC_ETH', rate=200, amount=1)
|
||||
order = sell(pair='ETH/BTC', rate=200, amount=1)
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
assert order['id'] == order_id
|
||||
|
||||
# test exception handling
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.InsufficientFunds)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
sell(pair='ETH/BTC', rate=200, amount=1)
|
||||
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
sell(pair='ETH/BTC', rate=200, amount=1)
|
||||
|
||||
with pytest.raises(TemporaryError):
|
||||
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
sell(pair='ETH/BTC', rate=200, amount=1)
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.BaseError)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
sell(pair='ETH/BTC', rate=200, amount=1)
|
||||
|
||||
|
||||
def test_get_balance_dry_run(default_conf, mocker):
|
||||
@@ -129,7 +219,7 @@ def test_get_balance_dry_run(default_conf, mocker):
|
||||
|
||||
def test_get_balance_prod(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
api_mock.get_balance = MagicMock(return_value=123.4)
|
||||
api_mock.fetch_balance = MagicMock(return_value={'BTC': {'free': 123.4}})
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
|
||||
default_conf['dry_run'] = False
|
||||
@@ -137,36 +227,53 @@ def test_get_balance_prod(default_conf, mocker):
|
||||
|
||||
assert get_balance(currency='BTC') == 123.4
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
get_balance(currency='BTC')
|
||||
|
||||
|
||||
def test_get_balances_dry_run(default_conf, mocker):
|
||||
default_conf['dry_run'] = True
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||
|
||||
assert get_balances() == []
|
||||
assert get_balances() == {}
|
||||
|
||||
|
||||
def test_get_balances_prod(default_conf, mocker):
|
||||
balance_item = {
|
||||
'Currency': '1ST',
|
||||
'Balance': 10.0,
|
||||
'Available': 10.0,
|
||||
'Pending': 0.0,
|
||||
'CryptoAddress': None
|
||||
'free': 10.0,
|
||||
'total': 10.0,
|
||||
'used': 0.0
|
||||
}
|
||||
|
||||
api_mock = MagicMock()
|
||||
api_mock.get_balances = MagicMock(
|
||||
return_value=[balance_item, balance_item, balance_item])
|
||||
api_mock.fetch_balance = MagicMock(return_value={
|
||||
'1ST': balance_item,
|
||||
'2ST': balance_item,
|
||||
'3ST': balance_item
|
||||
})
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
|
||||
default_conf['dry_run'] = False
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||
|
||||
assert len(get_balances()) == 3
|
||||
assert get_balances()[0]['Currency'] == '1ST'
|
||||
assert get_balances()[0]['Balance'] == 10.0
|
||||
assert get_balances()[0]['Available'] == 10.0
|
||||
assert get_balances()[0]['Pending'] == 0.0
|
||||
assert get_balances()['1ST']['free'] == 10.0
|
||||
assert get_balances()['1ST']['total'] == 10.0
|
||||
assert get_balances()['1ST']['used'] == 0.0
|
||||
|
||||
with pytest.raises(TemporaryError):
|
||||
api_mock.fetch_balance = MagicMock(side_effect=ccxt.NetworkError)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
get_balances()
|
||||
assert api_mock.fetch_balance.call_count == exchange.API_RETRY_COUNT + 1
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
get_balances()
|
||||
assert api_mock.fetch_balance.call_count == 1
|
||||
|
||||
|
||||
# This test is somewhat redundant with
|
||||
@@ -174,57 +281,123 @@ def test_get_balances_prod(default_conf, mocker):
|
||||
def test_get_ticker(default_conf, mocker):
|
||||
maybe_init_api(default_conf, mocker)
|
||||
api_mock = MagicMock()
|
||||
tick = {"success": True, 'result': {'Bid': 0.00001098, 'Ask': 0.00001099, 'Last': 0.0001}}
|
||||
api_mock.get_ticker = MagicMock(return_value=tick)
|
||||
mocker.patch('freqtrade.exchange.bittrex._API', api_mock)
|
||||
tick = {
|
||||
'symbol': 'ETH/BTC',
|
||||
'bid': 0.00001098,
|
||||
'ask': 0.00001099,
|
||||
'last': 0.0001,
|
||||
}
|
||||
api_mock.fetch_ticker = MagicMock(return_value=tick)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
|
||||
# retrieve original ticker
|
||||
ticker = get_ticker(pair='BTC_ETH')
|
||||
ticker = get_ticker(pair='ETH/BTC')
|
||||
|
||||
assert ticker['bid'] == 0.00001098
|
||||
assert ticker['ask'] == 0.00001099
|
||||
|
||||
# change the ticker
|
||||
tick = {"success": True, 'result': {"Bid": 0.5, "Ask": 1, "Last": 42}}
|
||||
api_mock.get_ticker = MagicMock(return_value=tick)
|
||||
mocker.patch('freqtrade.exchange.bittrex._API', api_mock)
|
||||
tick = {
|
||||
'symbol': 'ETH/BTC',
|
||||
'bid': 0.5,
|
||||
'ask': 1,
|
||||
'last': 42,
|
||||
}
|
||||
api_mock.fetch_ticker = MagicMock(return_value=tick)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
|
||||
# if not caching the result we should get the same ticker
|
||||
# if not fetching a new result we should get the cached ticker
|
||||
ticker = get_ticker(pair='BTC_ETH', refresh=False)
|
||||
assert ticker['bid'] == 0.00001098
|
||||
assert ticker['ask'] == 0.00001099
|
||||
ticker = get_ticker(pair='ETH/BTC')
|
||||
|
||||
# force ticker refresh
|
||||
ticker = get_ticker(pair='BTC_ETH', refresh=True)
|
||||
assert ticker['bid'] == 0.5
|
||||
assert ticker['ask'] == 1
|
||||
|
||||
with pytest.raises(TemporaryError): # test retrier
|
||||
api_mock.fetch_ticker = MagicMock(side_effect=ccxt.NetworkError)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
get_ticker(pair='ETH/BTC', refresh=True)
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
api_mock.fetch_ticker = MagicMock(side_effect=ccxt.BaseError)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
get_ticker(pair='ETH/BTC', refresh=True)
|
||||
|
||||
|
||||
def make_fetch_ohlcv_mock(data):
|
||||
def fetch_ohlcv_mock(pair, timeframe, since):
|
||||
if since:
|
||||
assert since > data[-1][0]
|
||||
return []
|
||||
return data
|
||||
return fetch_ohlcv_mock
|
||||
|
||||
|
||||
def test_get_ticker_history(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
tick = 123
|
||||
api_mock.get_ticker_history = MagicMock(return_value=tick)
|
||||
tick = [
|
||||
[
|
||||
1511686200000, # unix timestamp ms
|
||||
1, # open
|
||||
2, # high
|
||||
3, # low
|
||||
4, # close
|
||||
5, # volume (in quote currency)
|
||||
]
|
||||
]
|
||||
type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True})
|
||||
api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick))
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
|
||||
# retrieve original ticker
|
||||
ticks = get_ticker_history('BTC_ETH', int(default_conf['ticker_interval']))
|
||||
assert ticks == 123
|
||||
ticks = get_ticker_history('ETH/BTC', default_conf['ticker_interval'])
|
||||
assert ticks[0][0] == 1511686200000
|
||||
assert ticks[0][1] == 1
|
||||
assert ticks[0][2] == 2
|
||||
assert ticks[0][3] == 3
|
||||
assert ticks[0][4] == 4
|
||||
assert ticks[0][5] == 5
|
||||
|
||||
# change the ticker
|
||||
tick = 999
|
||||
api_mock.get_ticker_history = MagicMock(return_value=tick)
|
||||
# change ticker and ensure tick changes
|
||||
new_tick = [
|
||||
[
|
||||
1511686210000, # unix timestamp ms
|
||||
6, # open
|
||||
7, # high
|
||||
8, # low
|
||||
9, # close
|
||||
10, # volume (in quote currency)
|
||||
]
|
||||
]
|
||||
api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(new_tick))
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
|
||||
# ensure caching will still return the original ticker
|
||||
ticks = get_ticker_history('BTC_ETH', int(default_conf['ticker_interval']))
|
||||
assert ticks == 123
|
||||
ticks = get_ticker_history('ETH/BTC', default_conf['ticker_interval'])
|
||||
assert ticks[0][0] == 1511686210000
|
||||
assert ticks[0][1] == 6
|
||||
assert ticks[0][2] == 7
|
||||
assert ticks[0][3] == 8
|
||||
assert ticks[0][4] == 9
|
||||
assert ticks[0][5] == 10
|
||||
|
||||
with pytest.raises(TemporaryError): # test retrier
|
||||
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NetworkError)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
# new symbol to get around cache
|
||||
get_ticker_history('ABCD/BTC', default_conf['ticker_interval'])
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
# new symbol to get around cache
|
||||
get_ticker_history('EFGH/BTC', default_conf['ticker_interval'])
|
||||
|
||||
|
||||
def test_cancel_order_dry_run(default_conf, mocker):
|
||||
default_conf['dry_run'] = True
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||
|
||||
assert cancel_order(order_id='123') is None
|
||||
assert cancel_order(order_id='123', pair='TKN/BTC') is None
|
||||
|
||||
|
||||
# Ensure that if not dry_run, we should call API
|
||||
@@ -234,7 +407,25 @@ def test_cancel_order(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
api_mock.cancel_order = MagicMock(return_value=123)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
assert cancel_order(order_id='_') == 123
|
||||
assert cancel_order(order_id='_', pair='TKN/BTC') == 123
|
||||
|
||||
with pytest.raises(TemporaryError):
|
||||
api_mock.cancel_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
cancel_order(order_id='_', pair='TKN/BTC')
|
||||
assert api_mock.cancel_order.call_count == exchange.API_RETRY_COUNT + 1
|
||||
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
cancel_order(order_id='_', pair='TKN/BTC')
|
||||
assert api_mock.cancel_order.call_count == exchange.API_RETRY_COUNT + 1
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
api_mock.cancel_order = MagicMock(side_effect=ccxt.BaseError)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
cancel_order(order_id='_', pair='TKN/BTC')
|
||||
assert api_mock.cancel_order.call_count == 1
|
||||
|
||||
|
||||
def test_get_order(default_conf, mocker):
|
||||
@@ -243,44 +434,93 @@ def test_get_order(default_conf, mocker):
|
||||
order = MagicMock()
|
||||
order.myid = 123
|
||||
exchange._DRY_RUN_OPEN_ORDERS['X'] = order
|
||||
print(exchange.get_order('X'))
|
||||
assert exchange.get_order('X').myid == 123
|
||||
print(exchange.get_order('X', 'TKN/BTC'))
|
||||
assert exchange.get_order('X', 'TKN/BTC').myid == 123
|
||||
|
||||
default_conf['dry_run'] = False
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||
api_mock = MagicMock()
|
||||
api_mock.get_order = MagicMock(return_value=456)
|
||||
api_mock.fetch_order = MagicMock(return_value=456)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
assert exchange.get_order('X') == 456
|
||||
assert exchange.get_order('X', 'TKN/BTC') == 456
|
||||
|
||||
with pytest.raises(TemporaryError):
|
||||
api_mock.fetch_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
exchange.get_order(order_id='_', pair='TKN/BTC')
|
||||
assert api_mock.fetch_order.call_count == exchange.API_RETRY_COUNT + 1
|
||||
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
exchange.get_order(order_id='_', pair='TKN/BTC')
|
||||
assert api_mock.fetch_order.call_count == exchange.API_RETRY_COUNT + 1
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
api_mock.fetch_order = MagicMock(side_effect=ccxt.BaseError)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
exchange.get_order(order_id='_', pair='TKN/BTC')
|
||||
assert api_mock.fetch_order.call_count == 1
|
||||
|
||||
|
||||
def test_get_name(default_conf, mocker):
|
||||
mocker.patch('freqtrade.exchange.validate_pairs',
|
||||
side_effect=lambda s: True)
|
||||
default_conf['exchange']['name'] = 'binance'
|
||||
init(default_conf)
|
||||
|
||||
assert get_name() == 'Binance'
|
||||
|
||||
|
||||
def test_get_id(default_conf, mocker):
|
||||
mocker.patch('freqtrade.exchange.validate_pairs',
|
||||
side_effect=lambda s: True)
|
||||
default_conf['exchange']['name'] = 'binance'
|
||||
init(default_conf)
|
||||
|
||||
assert get_id() == 'binance'
|
||||
|
||||
|
||||
def test_get_pair_detail_url(default_conf, mocker):
|
||||
mocker.patch('freqtrade.exchange.validate_pairs',
|
||||
side_effect=lambda s: True)
|
||||
default_conf['exchange']['name'] = 'binance'
|
||||
init(default_conf)
|
||||
|
||||
url = get_pair_detail_url('TKN/ETH')
|
||||
assert 'TKN' in url
|
||||
assert 'ETH' in url
|
||||
|
||||
url = get_pair_detail_url('LOOONG/BTC')
|
||||
assert 'LOOONG' in url
|
||||
assert 'BTC' in url
|
||||
|
||||
default_conf['exchange']['name'] = 'bittrex'
|
||||
init(default_conf)
|
||||
|
||||
assert get_name() == 'Bittrex'
|
||||
url = get_pair_detail_url('TKN/ETH')
|
||||
assert 'TKN' in url
|
||||
assert 'ETH' in url
|
||||
|
||||
url = get_pair_detail_url('LOOONG/BTC')
|
||||
assert 'LOOONG' in url
|
||||
assert 'BTC' in url
|
||||
|
||||
|
||||
def test_get_fee(default_conf, mocker):
|
||||
mocker.patch('freqtrade.exchange.validate_pairs',
|
||||
side_effect=lambda s: True)
|
||||
init(default_conf)
|
||||
|
||||
assert get_fee() == 0.0025
|
||||
|
||||
|
||||
def test_exchange_misc(mocker):
|
||||
api_mock = MagicMock()
|
||||
api_mock.calculate_fee = MagicMock(return_value={
|
||||
'type': 'taker',
|
||||
'currency': 'BTC',
|
||||
'rate': 0.025,
|
||||
'cost': 0.05
|
||||
})
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
exchange.get_markets()
|
||||
assert api_mock.get_markets.call_count == 1
|
||||
exchange.get_market_summaries()
|
||||
assert api_mock.get_market_summaries.call_count == 1
|
||||
api_mock.name = 123
|
||||
assert exchange.get_name() == 123
|
||||
api_mock.fee = 456
|
||||
assert exchange.get_fee() == 456
|
||||
exchange.get_wallet_health()
|
||||
assert api_mock.get_wallet_health.call_count == 1
|
||||
assert get_fee() == 0.025
|
||||
|
||||
|
||||
def test_get_amount_lots(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
api_mock.amount_to_lots = MagicMock(return_value=1.0)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
assert get_amount_lots('LTC/BTC', 1.54) == 1
|
||||
|
@@ -1,349 +0,0 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103, protected-access, unused-argument
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from requests.exceptions import ContentDecodingError
|
||||
|
||||
import freqtrade.exchange.bittrex as btx
|
||||
from freqtrade.exchange.bittrex import Bittrex
|
||||
|
||||
|
||||
# Eat this flake8
|
||||
# +------------------+
|
||||
# | bittrex.Bittrex |
|
||||
# +------------------+
|
||||
# |
|
||||
# (mock Fake_bittrex)
|
||||
# |
|
||||
# +-----------------------------+
|
||||
# | freqtrade.exchange.Bittrex |
|
||||
# +-----------------------------+
|
||||
# Call into Bittrex will flow up to the
|
||||
# external package bittrex.Bittrex.
|
||||
# By inserting a mock, we redirect those
|
||||
# calls.
|
||||
# The faked bittrex API is called just 'fb'
|
||||
# The freqtrade.exchange.Bittrex is a
|
||||
# wrapper, and is called 'wb'
|
||||
|
||||
|
||||
def _stub_config():
|
||||
return {'key': '',
|
||||
'secret': ''}
|
||||
|
||||
|
||||
class FakeBittrex():
|
||||
def __init__(self, success=True):
|
||||
self.success = True # Believe in yourself
|
||||
self.result = None
|
||||
self.get_ticker_call_count = 0
|
||||
# This is really ugly, doing side-effect during instance creation
|
||||
# But we're allowed to in testing-code
|
||||
btx._API = MagicMock()
|
||||
btx._API.buy_limit = self.fake_buysell_limit
|
||||
btx._API.sell_limit = self.fake_buysell_limit
|
||||
btx._API.get_balance = self.fake_get_balance
|
||||
btx._API.get_balances = self.fake_get_balances
|
||||
btx._API.get_ticker = self.fake_get_ticker
|
||||
btx._API.get_order = self.fake_get_order
|
||||
btx._API.cancel = self.fake_cancel_order
|
||||
btx._API.get_markets = self.fake_get_markets
|
||||
btx._API.get_market_summaries = self.fake_get_market_summaries
|
||||
btx._API_V2 = MagicMock()
|
||||
btx._API_V2.get_candles = self.fake_get_candles
|
||||
btx._API_V2.get_wallet_health = self.fake_get_wallet_health
|
||||
|
||||
def fake_buysell_limit(self, pair, amount, limit):
|
||||
return {'success': self.success,
|
||||
'result': {'uuid': '1234'},
|
||||
'message': 'barter'}
|
||||
|
||||
def fake_get_balance(self, cur):
|
||||
return {'success': self.success,
|
||||
'result': {'Balance': 1234},
|
||||
'message': 'unbalanced'}
|
||||
|
||||
def fake_get_balances(self):
|
||||
return {'success': self.success,
|
||||
'result': [{'BTC_ETH': 1234}],
|
||||
'message': 'no balances'}
|
||||
|
||||
def fake_get_ticker(self, pair):
|
||||
self.get_ticker_call_count += 1
|
||||
return self.result or {'success': self.success,
|
||||
'result': {'Bid': 1, 'Ask': 1, 'Last': 1},
|
||||
'message': 'NO_API_RESPONSE'}
|
||||
|
||||
def fake_get_candles(self, pair, interval):
|
||||
return self.result or {'success': self.success,
|
||||
'result': [{'C': 0, 'V': 0, 'O': 0, 'H': 0, 'L': 0, 'T': 0}],
|
||||
'message': 'candles lit'}
|
||||
|
||||
def fake_get_order(self, uuid):
|
||||
return {'success': self.success,
|
||||
'result': {'OrderUuid': 'ABC123',
|
||||
'Type': 'Type',
|
||||
'Exchange': 'BTC_ETH',
|
||||
'Opened': True,
|
||||
'PricePerUnit': 1,
|
||||
'Quantity': 1,
|
||||
'QuantityRemaining': 1,
|
||||
'Closed': True},
|
||||
'message': 'lost'}
|
||||
|
||||
def fake_cancel_order(self, uuid):
|
||||
return self.result or {'success': self.success,
|
||||
'message': 'no such order'}
|
||||
|
||||
def fake_get_markets(self):
|
||||
return self.result or {'success': self.success,
|
||||
'message': 'market gone',
|
||||
'result': [{'MarketName': '-_'}]}
|
||||
|
||||
def fake_get_market_summaries(self):
|
||||
return self.result or {'success': self.success,
|
||||
'message': 'no summary',
|
||||
'result': ['sum']}
|
||||
|
||||
def fake_get_wallet_health(self):
|
||||
return self.result or {'success': self.success,
|
||||
'message': 'bad health',
|
||||
'result': [{'Health': {'Currency': 'BTC_ETH',
|
||||
'IsActive': True,
|
||||
'LastChecked': 0},
|
||||
'Currency': {'Notice': True}}]}
|
||||
|
||||
|
||||
# The freqtrade.exchange.bittrex is called wrap_bittrex
|
||||
# to not confuse naming with bittrex.bittrex
|
||||
def make_wrap_bittrex():
|
||||
conf = _stub_config()
|
||||
wb = btx.Bittrex(conf)
|
||||
return wb
|
||||
|
||||
|
||||
def test_exchange_bittrex_class():
|
||||
conf = _stub_config()
|
||||
b = Bittrex(conf)
|
||||
assert isinstance(b, Bittrex)
|
||||
slots = dir(b)
|
||||
for name in ['fee', 'buy', 'sell', 'get_balance', 'get_balances',
|
||||
'get_ticker', 'get_ticker_history', 'get_order',
|
||||
'cancel_order', 'get_pair_detail_url', 'get_markets',
|
||||
'get_market_summaries', 'get_wallet_health']:
|
||||
assert name in slots
|
||||
# FIX: ensure that the slot is also a method in the class
|
||||
# getattr(b, name) => bound method Bittrex.buy
|
||||
# type(getattr(b, name)) => class 'method'
|
||||
|
||||
|
||||
def test_exchange_bittrex_fee():
|
||||
fee = Bittrex.fee.__get__(Bittrex)
|
||||
assert fee >= 0 and fee < 0.1 # Fee is 0-10 %
|
||||
|
||||
|
||||
def test_exchange_bittrex_buy_good():
|
||||
wb = make_wrap_bittrex()
|
||||
fb = FakeBittrex()
|
||||
uuid = wb.buy('BTC_ETH', 1, 1)
|
||||
assert uuid == fb.fake_buysell_limit(1, 2, 3)['result']['uuid']
|
||||
|
||||
fb.success = False
|
||||
with pytest.raises(btx.OperationalException, match=r'barter.*'):
|
||||
wb.buy('BAD', 1, 1)
|
||||
|
||||
|
||||
def test_exchange_bittrex_sell_good():
|
||||
wb = make_wrap_bittrex()
|
||||
fb = FakeBittrex()
|
||||
uuid = wb.sell('BTC_ETH', 1, 1)
|
||||
assert uuid == fb.fake_buysell_limit(1, 2, 3)['result']['uuid']
|
||||
|
||||
fb.success = False
|
||||
with pytest.raises(btx.OperationalException, match=r'barter.*'):
|
||||
uuid = wb.sell('BAD', 1, 1)
|
||||
|
||||
|
||||
def test_exchange_bittrex_get_balance():
|
||||
wb = make_wrap_bittrex()
|
||||
fb = FakeBittrex()
|
||||
bal = wb.get_balance('BTC_ETH')
|
||||
assert bal == fb.fake_get_balance(1)['result']['Balance']
|
||||
|
||||
fb.success = False
|
||||
with pytest.raises(btx.OperationalException, match=r'unbalanced'):
|
||||
wb.get_balance('BTC_ETH')
|
||||
|
||||
|
||||
def test_exchange_bittrex_get_balances():
|
||||
wb = make_wrap_bittrex()
|
||||
fb = FakeBittrex()
|
||||
bals = wb.get_balances()
|
||||
assert bals == fb.fake_get_balances()['result']
|
||||
|
||||
fb.success = False
|
||||
with pytest.raises(btx.OperationalException, match=r'no balances'):
|
||||
wb.get_balances()
|
||||
|
||||
|
||||
def test_exchange_bittrex_get_ticker():
|
||||
wb = make_wrap_bittrex()
|
||||
fb = FakeBittrex()
|
||||
|
||||
# Poll ticker, which updates the cache
|
||||
tick = wb.get_ticker('BTC_ETH')
|
||||
for x in ['bid', 'ask', 'last']:
|
||||
assert x in tick
|
||||
# Ensure the side-effect was made (update the ticker cache)
|
||||
assert 'BTC_ETH' in wb.cached_ticker.keys()
|
||||
|
||||
# taint the cache, so we can recognize the cache wall utilized
|
||||
wb.cached_ticker['BTC_ETH']['bid'] = 1234
|
||||
# Poll again, getting the cached result
|
||||
fb.get_ticker_call_count = 0
|
||||
tick = wb.get_ticker('BTC_ETH', False)
|
||||
# Ensure the result was from the cache, and that we didn't call exchange
|
||||
assert wb.cached_ticker['BTC_ETH']['bid'] == 1234
|
||||
assert fb.get_ticker_call_count == 0
|
||||
|
||||
|
||||
def test_exchange_bittrex_get_ticker_bad():
|
||||
wb = make_wrap_bittrex()
|
||||
fb = FakeBittrex()
|
||||
fb.result = {'success': True, 'result': {'Bid': 1, 'Ask': 0}} # incomplete result
|
||||
|
||||
with pytest.raises(ContentDecodingError, match=r'.*Invalid response from Bittrex params.*'):
|
||||
wb.get_ticker('BTC_ETH')
|
||||
fb.result = {'success': False, 'message': 'gone bad'}
|
||||
with pytest.raises(btx.OperationalException, match=r'.*gone bad.*'):
|
||||
wb.get_ticker('BTC_ETH')
|
||||
|
||||
fb.result = {'success': True, 'result': {}} # incomplete result
|
||||
with pytest.raises(ContentDecodingError, match=r'.*Invalid response from Bittrex params.*'):
|
||||
wb.get_ticker('BTC_ETH')
|
||||
fb.result = {'success': False, 'message': 'gone bad'}
|
||||
with pytest.raises(btx.OperationalException, match=r'.*gone bad.*'):
|
||||
wb.get_ticker('BTC_ETH')
|
||||
|
||||
fb.result = {'success': True,
|
||||
'result': {'Bid': 1, 'Ask': 0, 'Last': None}} # incomplete result
|
||||
with pytest.raises(ContentDecodingError, match=r'.*Invalid response from Bittrex params.*'):
|
||||
wb.get_ticker('BTC_ETH')
|
||||
|
||||
|
||||
def test_exchange_bittrex_get_ticker_history_intervals():
|
||||
wb = make_wrap_bittrex()
|
||||
FakeBittrex()
|
||||
for tick_interval in [1, 5, 30, 60, 1440]:
|
||||
assert ([{'C': 0, 'V': 0, 'O': 0, 'H': 0, 'L': 0, 'T': 0}] ==
|
||||
wb.get_ticker_history('BTC_ETH', tick_interval))
|
||||
|
||||
|
||||
def test_exchange_bittrex_get_ticker_history():
|
||||
wb = make_wrap_bittrex()
|
||||
fb = FakeBittrex()
|
||||
assert wb.get_ticker_history('BTC_ETH', 5)
|
||||
with pytest.raises(ValueError, match=r'.*Unknown tick_interval.*'):
|
||||
wb.get_ticker_history('BTC_ETH', 2)
|
||||
|
||||
fb.success = False
|
||||
with pytest.raises(btx.OperationalException, match=r'candles lit.*'):
|
||||
wb.get_ticker_history('BTC_ETH', 5)
|
||||
|
||||
fb.success = True
|
||||
with pytest.raises(ContentDecodingError, match=r'.*Invalid response from Bittrex.*'):
|
||||
fb.result = {'bad': 0}
|
||||
wb.get_ticker_history('BTC_ETH', 5)
|
||||
|
||||
with pytest.raises(ContentDecodingError, match=r'.*Required property C not present.*'):
|
||||
fb.result = {'success': True,
|
||||
'result': [{'V': 0, 'O': 0, 'H': 0, 'L': 0, 'T': 0}], # close is missing
|
||||
'message': 'candles lit'}
|
||||
wb.get_ticker_history('BTC_ETH', 5)
|
||||
|
||||
|
||||
def test_exchange_bittrex_get_order():
|
||||
wb = make_wrap_bittrex()
|
||||
fb = FakeBittrex()
|
||||
order = wb.get_order('someUUID')
|
||||
assert order['id'] == 'ABC123'
|
||||
fb.success = False
|
||||
with pytest.raises(btx.OperationalException, match=r'lost'):
|
||||
wb.get_order('someUUID')
|
||||
|
||||
|
||||
def test_exchange_bittrex_cancel_order():
|
||||
wb = make_wrap_bittrex()
|
||||
fb = FakeBittrex()
|
||||
wb.cancel_order('someUUID')
|
||||
with pytest.raises(btx.OperationalException, match=r'no such order'):
|
||||
fb.success = False
|
||||
wb.cancel_order('someUUID')
|
||||
# Note: this can be a bug in exchange.bittrex._validate_response
|
||||
with pytest.raises(KeyError):
|
||||
fb.result = {'success': False} # message is missing!
|
||||
wb.cancel_order('someUUID')
|
||||
with pytest.raises(btx.OperationalException, match=r'foo'):
|
||||
fb.result = {'success': False, 'message': 'foo'}
|
||||
wb.cancel_order('someUUID')
|
||||
|
||||
|
||||
def test_exchange_get_pair_detail_url():
|
||||
wb = make_wrap_bittrex()
|
||||
assert wb.get_pair_detail_url('BTC_ETH')
|
||||
|
||||
|
||||
def test_exchange_get_markets():
|
||||
wb = make_wrap_bittrex()
|
||||
fb = FakeBittrex()
|
||||
x = wb.get_markets()
|
||||
assert x == ['__']
|
||||
with pytest.raises(btx.OperationalException, match=r'market gone'):
|
||||
fb.success = False
|
||||
wb.get_markets()
|
||||
|
||||
|
||||
def test_exchange_get_market_summaries():
|
||||
wb = make_wrap_bittrex()
|
||||
fb = FakeBittrex()
|
||||
assert ['sum'] == wb.get_market_summaries()
|
||||
with pytest.raises(btx.OperationalException, match=r'no summary'):
|
||||
fb.success = False
|
||||
wb.get_market_summaries()
|
||||
|
||||
|
||||
def test_exchange_get_wallet_health():
|
||||
wb = make_wrap_bittrex()
|
||||
fb = FakeBittrex()
|
||||
x = wb.get_wallet_health()
|
||||
assert x[0]['Currency'] == 'BTC_ETH'
|
||||
with pytest.raises(btx.OperationalException, match=r'bad health'):
|
||||
fb.success = False
|
||||
wb.get_wallet_health()
|
||||
|
||||
|
||||
def test_validate_response_success():
|
||||
response = {
|
||||
'message': '',
|
||||
'result': [],
|
||||
}
|
||||
Bittrex._validate_response(response)
|
||||
|
||||
|
||||
def test_validate_response_no_api_response():
|
||||
response = {
|
||||
'message': 'NO_API_RESPONSE',
|
||||
'result': None,
|
||||
}
|
||||
with pytest.raises(ContentDecodingError, match=r'.*NO_API_RESPONSE.*'):
|
||||
Bittrex._validate_response(response)
|
||||
|
||||
|
||||
def test_validate_response_min_trade_requirement_not_met():
|
||||
response = {
|
||||
'message': 'MIN_TRADE_REQUIREMENT_NOT_MET',
|
||||
'result': None,
|
||||
}
|
||||
with pytest.raises(ContentDecodingError, match=r'.*MIN_TRADE_REQUIREMENT_NOT_MET.*'):
|
||||
Bittrex._validate_response(response)
|
@@ -15,10 +15,7 @@ from freqtrade import optimize
|
||||
from freqtrade.analyze import Analyze
|
||||
from freqtrade.arguments import Arguments
|
||||
from freqtrade.optimize.backtesting import Backtesting, start, setup_configuration
|
||||
from freqtrade.tests.conftest import default_conf, log_has
|
||||
|
||||
# Avoid to reinit the same object again and again
|
||||
_BACKTESTING = Backtesting(default_conf())
|
||||
from freqtrade.tests.conftest import log_has
|
||||
|
||||
|
||||
def get_args(args) -> List[str]:
|
||||
@@ -34,49 +31,60 @@ def trim_dictlist(dict_list, num):
|
||||
|
||||
def load_data_test(what):
|
||||
timerange = ((None, 'line'), None, -100)
|
||||
data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST'], timerange=timerange)
|
||||
pair = data['BTC_UNITEST']
|
||||
data = optimize.load_data(None, ticker_interval='1m',
|
||||
pairs=['UNITTEST/BTC'], timerange=timerange)
|
||||
pair = data['UNITTEST/BTC']
|
||||
datalen = len(pair)
|
||||
# Depending on the what parameter we now adjust the
|
||||
# loaded data looks:
|
||||
# pair :: [{'O': 0.123, 'H': 0.123, 'L': 0.123,
|
||||
# 'C': 0.123, 'V': 123.123,
|
||||
# 'T': '2017-11-04T23:02:00', 'BV': 0.123}]
|
||||
# pair :: [[ 1509836520000, unix timestamp in ms
|
||||
# 0.00162008, open
|
||||
# 0.00162008, high
|
||||
# 0.00162008, low
|
||||
# 0.00162008, close
|
||||
# 108.14853839 base volume
|
||||
# ]]
|
||||
base = 0.001
|
||||
if what == 'raise':
|
||||
return {'BTC_UNITEST':
|
||||
[{'T': pair[x]['T'], # Keep old dates
|
||||
'V': pair[x]['V'], # Keep old volume
|
||||
'BV': pair[x]['BV'], # keep too
|
||||
'O': x * base, # But replace O,H,L,C
|
||||
'H': x * base + 0.0001,
|
||||
'L': x * base - 0.0001,
|
||||
'C': x * base} for x in range(0, datalen)]}
|
||||
return {'UNITTEST/BTC': [
|
||||
[
|
||||
pair[x][0], # Keep old dates
|
||||
x * base, # But replace O,H,L,C
|
||||
x * base + 0.0001,
|
||||
x * base - 0.0001,
|
||||
x * base,
|
||||
pair[x][5], # Keep old volume
|
||||
] for x in range(0, datalen)
|
||||
]}
|
||||
if what == 'lower':
|
||||
return {'BTC_UNITEST':
|
||||
[{'T': pair[x]['T'], # Keep old dates
|
||||
'V': pair[x]['V'], # Keep old volume
|
||||
'BV': pair[x]['BV'], # keep too
|
||||
'O': 1 - x * base, # But replace O,H,L,C
|
||||
'H': 1 - x * base + 0.0001,
|
||||
'L': 1 - x * base - 0.0001,
|
||||
'C': 1 - x * base} for x in range(0, datalen)]}
|
||||
return {'UNITTEST/BTC': [
|
||||
[
|
||||
pair[x][0], # Keep old dates
|
||||
1 - x * base, # But replace O,H,L,C
|
||||
1 - x * base + 0.0001,
|
||||
1 - x * base - 0.0001,
|
||||
1 - x * base,
|
||||
pair[x][5] # Keep old volume
|
||||
] for x in range(0, datalen)
|
||||
]}
|
||||
if what == 'sine':
|
||||
hz = 0.1 # frequency
|
||||
return {'BTC_UNITEST':
|
||||
[{'T': pair[x]['T'], # Keep old dates
|
||||
'V': pair[x]['V'], # Keep old volume
|
||||
'BV': pair[x]['BV'], # keep too
|
||||
# But replace O,H,L,C
|
||||
'O': math.sin(x * hz) / 1000 + base,
|
||||
'H': math.sin(x * hz) / 1000 + base + 0.0001,
|
||||
'L': math.sin(x * hz) / 1000 + base - 0.0001,
|
||||
'C': math.sin(x * hz) / 1000 + base} for x in range(0, datalen)]}
|
||||
return {'UNITTEST/BTC': [
|
||||
[
|
||||
pair[x][0], # Keep old dates
|
||||
math.sin(x * hz) / 1000 + base, # But replace O,H,L,C
|
||||
math.sin(x * hz) / 1000 + base + 0.0001,
|
||||
math.sin(x * hz) / 1000 + base - 0.0001,
|
||||
math.sin(x * hz) / 1000 + base,
|
||||
pair[x][5] # Keep old volume
|
||||
] for x in range(0, datalen)
|
||||
]}
|
||||
return data
|
||||
|
||||
|
||||
def simple_backtest(config, contour, num_results) -> None:
|
||||
backtesting = _BACKTESTING
|
||||
def simple_backtest(config, contour, num_results, mocker) -> None:
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
backtesting = Backtesting(config)
|
||||
|
||||
data = load_data_test(contour)
|
||||
processed = backtesting.tickerdata_to_dataframe(data)
|
||||
@@ -93,9 +101,9 @@ def simple_backtest(config, contour, num_results) -> None:
|
||||
assert len(results) == num_results
|
||||
|
||||
|
||||
def mocked_load_data(datadir, pairs=[], ticker_interval=0, refresh_pairs=False, timerange=None):
|
||||
tickerdata = optimize.load_tickerdata_file(datadir, 'BTC_UNITEST', 1, timerange=timerange)
|
||||
pairdata = {'BTC_UNITEST': tickerdata}
|
||||
def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=False, timerange=None):
|
||||
tickerdata = optimize.load_tickerdata_file(datadir, 'UNITTEST/BTC', '1m', timerange=timerange)
|
||||
pairdata = {'UNITTEST/BTC': tickerdata}
|
||||
return pairdata
|
||||
|
||||
|
||||
@@ -107,12 +115,14 @@ def _load_pair_as_ticks(pair, tickfreq):
|
||||
|
||||
|
||||
# FIX: fixturize this?
|
||||
def _make_backtest_conf(conf=None, pair='BTC_UNITEST', record=None):
|
||||
data = optimize.load_data(None, ticker_interval=8, pairs=[pair])
|
||||
def _make_backtest_conf(mocker, conf=None, pair='UNITTEST/BTC', record=None):
|
||||
data = optimize.load_data(None, ticker_interval='8m', pairs=[pair])
|
||||
data = trim_dictlist(data, -200)
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
backtesting = Backtesting(conf)
|
||||
return {
|
||||
'stake_amount': conf['stake_amount'],
|
||||
'processed': _BACKTESTING.tickerdata_to_dataframe(data),
|
||||
'processed': backtesting.tickerdata_to_dataframe(data),
|
||||
'max_open_trades': 10,
|
||||
'realistic': True,
|
||||
'record': record
|
||||
@@ -148,21 +158,6 @@ def _trend_alternate(dataframe=None):
|
||||
return dataframe
|
||||
|
||||
|
||||
def _run_backtest_1(fun, backtest_conf):
|
||||
# strategy is a global (hidden as a singleton), so we
|
||||
# emulate strategy being pure, by override/restore here
|
||||
# if we dont do this, the override in strategy will carry over
|
||||
# to other tests
|
||||
old_buy = _BACKTESTING.populate_buy_trend
|
||||
old_sell = _BACKTESTING.populate_sell_trend
|
||||
_BACKTESTING.populate_buy_trend = fun # Override
|
||||
_BACKTESTING.populate_sell_trend = fun # Override
|
||||
results = _BACKTESTING.backtest(backtest_conf)
|
||||
_BACKTESTING.populate_buy_trend = old_buy # restore override
|
||||
_BACKTESTING.populate_sell_trend = old_sell # restore override
|
||||
return results
|
||||
|
||||
|
||||
# Unit tests
|
||||
def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None:
|
||||
"""
|
||||
@@ -218,7 +213,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'--datadir', '/foo/bar',
|
||||
'backtesting',
|
||||
'--ticker-interval', '1',
|
||||
'--ticker-interval', '1m',
|
||||
'--live',
|
||||
'--realistic-simulation',
|
||||
'--refresh-pairs-cached',
|
||||
@@ -240,18 +235,18 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
||||
assert 'ticker_interval' in config
|
||||
assert log_has('Parameter -i/--ticker-interval detected ...', caplog.record_tuples)
|
||||
assert log_has(
|
||||
'Using ticker_interval: 1 ...',
|
||||
'Using ticker_interval: 1m ...',
|
||||
caplog.record_tuples
|
||||
)
|
||||
|
||||
assert 'live' in config
|
||||
assert log_has('Parameter -l/--live detected ...', caplog.record_tuples)
|
||||
|
||||
assert 'realistic_simulation'in config
|
||||
assert 'realistic_simulation' in config
|
||||
assert log_has('Parameter --realistic-simulation detected ...', caplog.record_tuples)
|
||||
assert log_has('Using max_open_trades: 1 ...', caplog.record_tuples)
|
||||
|
||||
assert 'refresh_pairs'in config
|
||||
assert 'refresh_pairs' in config
|
||||
assert log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples)
|
||||
assert 'timerange' in config
|
||||
assert log_has(
|
||||
@@ -266,11 +261,13 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
||||
)
|
||||
|
||||
|
||||
def test_start(mocker, default_conf, caplog) -> None:
|
||||
def test_start(mocker, fee, default_conf, caplog) -> None:
|
||||
"""
|
||||
Test start() function
|
||||
"""
|
||||
start_mock = MagicMock()
|
||||
mocker.patch('freqtrade.exchange.get_fee', fee)
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.start', start_mock)
|
||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||
read_data=json.dumps(default_conf)
|
||||
@@ -306,49 +303,51 @@ def test_backtesting__init__(mocker, default_conf) -> None:
|
||||
assert init_mock.call_count == 1
|
||||
|
||||
|
||||
def test_backtesting_init(default_conf) -> None:
|
||||
def test_backtesting_init(mocker, default_conf) -> None:
|
||||
"""
|
||||
Test Backtesting._init() method
|
||||
"""
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
backtesting = Backtesting(default_conf)
|
||||
assert backtesting.config == default_conf
|
||||
assert isinstance(backtesting.analyze, Analyze)
|
||||
assert backtesting.ticker_interval == 5
|
||||
assert backtesting.ticker_interval == '5m'
|
||||
assert callable(backtesting.tickerdata_to_dataframe)
|
||||
assert callable(backtesting.populate_buy_trend)
|
||||
assert callable(backtesting.populate_sell_trend)
|
||||
|
||||
|
||||
def test_tickerdata_to_dataframe(default_conf) -> None:
|
||||
def test_tickerdata_to_dataframe(default_conf, mocker) -> None:
|
||||
"""
|
||||
Test Backtesting.tickerdata_to_dataframe() method
|
||||
"""
|
||||
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
timerange = ((None, 'line'), None, -100)
|
||||
tick = optimize.load_tickerdata_file(None, 'BTC_UNITEST', 1, timerange=timerange)
|
||||
tickerlist = {'BTC_UNITEST': tick}
|
||||
tick = optimize.load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange)
|
||||
tickerlist = {'UNITTEST/BTC': tick}
|
||||
|
||||
backtesting = _BACKTESTING
|
||||
backtesting = Backtesting(default_conf)
|
||||
data = backtesting.tickerdata_to_dataframe(tickerlist)
|
||||
assert len(data['BTC_UNITEST']) == 100
|
||||
assert len(data['UNITTEST/BTC']) == 100
|
||||
|
||||
# Load Analyze to compare the result between Backtesting function and Analyze are the same
|
||||
analyze = Analyze(default_conf)
|
||||
data2 = analyze.tickerdata_to_dataframe(tickerlist)
|
||||
assert data['BTC_UNITEST'].equals(data2['BTC_UNITEST'])
|
||||
assert data['UNITTEST/BTC'].equals(data2['UNITTEST/BTC'])
|
||||
|
||||
|
||||
def test_get_timeframe() -> None:
|
||||
def test_get_timeframe(default_conf, mocker) -> None:
|
||||
"""
|
||||
Test Backtesting.get_timeframe() method
|
||||
"""
|
||||
backtesting = _BACKTESTING
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
backtesting = Backtesting(default_conf)
|
||||
|
||||
data = backtesting.tickerdata_to_dataframe(
|
||||
optimize.load_data(
|
||||
None,
|
||||
ticker_interval=1,
|
||||
pairs=['BTC_UNITEST']
|
||||
ticker_interval='1m',
|
||||
pairs=['UNITTEST/BTC']
|
||||
)
|
||||
)
|
||||
min_date, max_date = backtesting.get_timeframe(data)
|
||||
@@ -356,15 +355,16 @@ def test_get_timeframe() -> None:
|
||||
assert max_date.isoformat() == '2017-11-14T22:59:00+00:00'
|
||||
|
||||
|
||||
def test_generate_text_table():
|
||||
def test_generate_text_table(default_conf, mocker):
|
||||
"""
|
||||
Test Backtesting.generate_text_table() method
|
||||
"""
|
||||
backtesting = _BACKTESTING
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
backtesting = Backtesting(default_conf)
|
||||
|
||||
results = pd.DataFrame(
|
||||
{
|
||||
'currency': ['BTC_ETH', 'BTC_ETH'],
|
||||
'currency': ['ETH/BTC', 'ETH/BTC'],
|
||||
'profit_percent': [0.1, 0.2],
|
||||
'profit_BTC': [0.2, 0.4],
|
||||
'duration': [10, 30],
|
||||
@@ -378,25 +378,27 @@ def test_generate_text_table():
|
||||
'total profit BTC avg duration profit loss\n'
|
||||
'------- ----------- -------------- '
|
||||
'------------------ -------------- -------- ------\n'
|
||||
'BTC_ETH 2 15.00 '
|
||||
'ETH/BTC 2 15.00 '
|
||||
'0.60000000 20.0 2 0\n'
|
||||
'TOTAL 2 15.00 '
|
||||
'0.60000000 20.0 2 0'
|
||||
)
|
||||
|
||||
assert backtesting._generate_text_table(data={'BTC_ETH': {}}, results=results) == result_str
|
||||
assert backtesting._generate_text_table(data={'ETH/BTC': {}}, results=results) == result_str
|
||||
|
||||
|
||||
def test_backtesting_start(default_conf, mocker, caplog) -> None:
|
||||
"""
|
||||
Test Backtesting.start() method
|
||||
"""
|
||||
|
||||
def get_timeframe(input1, input2):
|
||||
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.Analyze', MagicMock())
|
||||
mocker.patch('freqtrade.optimize.load_data', mocked_load_data)
|
||||
mocker.patch('freqtrade.exchange.get_ticker_history')
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.optimize.backtesting.Backtesting',
|
||||
backtest=MagicMock(),
|
||||
@@ -405,7 +407,7 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
|
||||
)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
conf['exchange']['pair_whitelist'] = ['BTC_UNITEST']
|
||||
conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
|
||||
conf['ticker_interval'] = 1
|
||||
conf['live'] = False
|
||||
conf['datadir'] = None
|
||||
@@ -426,13 +428,15 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
|
||||
assert log_has(line, caplog.record_tuples)
|
||||
|
||||
|
||||
def test_backtest(default_conf) -> None:
|
||||
def test_backtest(default_conf, fee, mocker) -> None:
|
||||
"""
|
||||
Test Backtesting.backtest() method
|
||||
"""
|
||||
backtesting = _BACKTESTING
|
||||
mocker.patch('freqtrade.exchange.get_fee', fee)
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
backtesting = Backtesting(default_conf)
|
||||
|
||||
data = optimize.load_data(None, ticker_interval=5, pairs=['BTC_ETH'])
|
||||
data = optimize.load_data(None, ticker_interval='5m', pairs=['UNITTEST/BTC'])
|
||||
data = trim_dictlist(data, -200)
|
||||
results = backtesting.backtest(
|
||||
{
|
||||
@@ -445,14 +449,16 @@ def test_backtest(default_conf) -> None:
|
||||
assert not results.empty
|
||||
|
||||
|
||||
def test_backtest_1min_ticker_interval(default_conf) -> None:
|
||||
def test_backtest_1min_ticker_interval(default_conf, fee, mocker) -> None:
|
||||
"""
|
||||
Test Backtesting.backtest() method with 1 min ticker
|
||||
"""
|
||||
backtesting = _BACKTESTING
|
||||
mocker.patch('freqtrade.exchange.get_fee', fee)
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
backtesting = Backtesting(default_conf)
|
||||
|
||||
# Run a backtesting for an exiting 5min ticker_interval
|
||||
data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST'])
|
||||
data = optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC'])
|
||||
data = trim_dictlist(data, -200)
|
||||
results = backtesting.backtest(
|
||||
{
|
||||
@@ -465,15 +471,16 @@ def test_backtest_1min_ticker_interval(default_conf) -> None:
|
||||
assert not results.empty
|
||||
|
||||
|
||||
def test_processed() -> None:
|
||||
def test_processed(default_conf, mocker) -> None:
|
||||
"""
|
||||
Test Backtesting.backtest() method with offline data
|
||||
"""
|
||||
backtesting = _BACKTESTING
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
backtesting = Backtesting(default_conf)
|
||||
|
||||
dict_of_tickerrows = load_data_test('raise')
|
||||
dataframes = backtesting.tickerdata_to_dataframe(dict_of_tickerrows)
|
||||
dataframe = dataframes['BTC_UNITEST']
|
||||
dataframe = dataframes['UNITTEST/BTC']
|
||||
cols = dataframe.columns
|
||||
# assert the dataframe got some of the indicator columns
|
||||
for col in ['close', 'high', 'low', 'open', 'date',
|
||||
@@ -481,76 +488,101 @@ def test_processed() -> None:
|
||||
assert col in cols
|
||||
|
||||
|
||||
def test_backtest_pricecontours(default_conf) -> None:
|
||||
def test_backtest_pricecontours(default_conf, fee, mocker) -> None:
|
||||
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
|
||||
tests = [['raise', 17], ['lower', 0], ['sine', 17]]
|
||||
for [contour, numres] in tests:
|
||||
simple_backtest(default_conf, contour, numres)
|
||||
simple_backtest(default_conf, contour, numres, mocker)
|
||||
|
||||
|
||||
# Test backtest using offline data (testdata directory)
|
||||
def test_backtest_ticks(default_conf):
|
||||
def test_backtest_ticks(default_conf, fee, mocker):
|
||||
mocker.patch('freqtrade.exchange.get_fee', fee)
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
ticks = [1, 5]
|
||||
fun = _BACKTESTING.populate_buy_trend
|
||||
for tick in ticks:
|
||||
backtest_conf = _make_backtest_conf(conf=default_conf)
|
||||
results = _run_backtest_1(fun, backtest_conf)
|
||||
fun = Backtesting(default_conf).populate_buy_trend
|
||||
for _ in ticks:
|
||||
backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting.populate_buy_trend = fun # Override
|
||||
backtesting.populate_sell_trend = fun # Override
|
||||
results = backtesting.backtest(backtest_conf)
|
||||
assert not results.empty
|
||||
|
||||
|
||||
def test_backtest_clash_buy_sell(default_conf):
|
||||
# Override the default buy trend function in our DefaultStrategy
|
||||
def test_backtest_clash_buy_sell(mocker, default_conf):
|
||||
# Override the default buy trend function in our default_strategy
|
||||
def fun(dataframe=None):
|
||||
buy_value = 1
|
||||
sell_value = 1
|
||||
return _trend(dataframe, buy_value, sell_value)
|
||||
|
||||
backtest_conf = _make_backtest_conf(conf=default_conf)
|
||||
results = _run_backtest_1(fun, backtest_conf)
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting.populate_buy_trend = fun # Override
|
||||
backtesting.populate_sell_trend = fun # Override
|
||||
results = backtesting.backtest(backtest_conf)
|
||||
assert results.empty
|
||||
|
||||
|
||||
def test_backtest_only_sell(default_conf):
|
||||
# Override the default buy trend function in our DefaultStrategy
|
||||
def test_backtest_only_sell(mocker, default_conf):
|
||||
# Override the default buy trend function in our default_strategy
|
||||
def fun(dataframe=None):
|
||||
buy_value = 0
|
||||
sell_value = 1
|
||||
return _trend(dataframe, buy_value, sell_value)
|
||||
|
||||
backtest_conf = _make_backtest_conf(conf=default_conf)
|
||||
results = _run_backtest_1(fun, backtest_conf)
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting.populate_buy_trend = fun # Override
|
||||
backtesting.populate_sell_trend = fun # Override
|
||||
results = backtesting.backtest(backtest_conf)
|
||||
assert results.empty
|
||||
|
||||
|
||||
def test_backtest_alternate_buy_sell(default_conf):
|
||||
backtest_conf = _make_backtest_conf(conf=default_conf, pair='BTC_UNITEST')
|
||||
results = _run_backtest_1(_trend_alternate, backtest_conf)
|
||||
def test_backtest_alternate_buy_sell(default_conf, fee, mocker):
|
||||
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC')
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting.populate_buy_trend = _trend_alternate # Override
|
||||
backtesting.populate_sell_trend = _trend_alternate # Override
|
||||
results = backtesting.backtest(backtest_conf)
|
||||
assert len(results) == 3
|
||||
|
||||
|
||||
def test_backtest_record(default_conf, mocker):
|
||||
def test_backtest_record(default_conf, fee, mocker):
|
||||
names = []
|
||||
records = []
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
|
||||
mocker.patch(
|
||||
'freqtrade.optimize.backtesting.file_dump_json',
|
||||
new=lambda n, r: (names.append(n), records.append(r))
|
||||
)
|
||||
backtest_conf = _make_backtest_conf(
|
||||
mocker,
|
||||
conf=default_conf,
|
||||
pair='BTC_UNITEST',
|
||||
pair='UNITTEST/BTC',
|
||||
record="trades"
|
||||
)
|
||||
results = _run_backtest_1(_trend_alternate, backtest_conf)
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting.populate_buy_trend = _trend_alternate # Override
|
||||
backtesting.populate_sell_trend = _trend_alternate # Override
|
||||
results = backtesting.backtest(backtest_conf)
|
||||
assert len(results) == 3
|
||||
# Assert file_dump_json was only called once
|
||||
assert names == ['backtest-result.json']
|
||||
records = records[0]
|
||||
# Ensure records are of correct type
|
||||
assert len(records) == 3
|
||||
# ('BTC_UNITEST', 0.00331158, '1510684320', '1510691700', 0, 117)
|
||||
# ('UNITTEST/BTC', 0.00331158, '1510684320', '1510691700', 0, 117)
|
||||
# Below follows just a typecheck of the schema/type of trade-records
|
||||
oix = None
|
||||
for (pair, profit, date_buy, date_sell, buy_index, dur) in records:
|
||||
assert pair == 'BTC_UNITEST'
|
||||
assert pair == 'UNITTEST/BTC'
|
||||
isinstance(profit, float)
|
||||
# FIX: buy/sell should be converted to ints
|
||||
isinstance(date_buy, str)
|
||||
@@ -563,13 +595,15 @@ def test_backtest_record(default_conf, mocker):
|
||||
|
||||
|
||||
def test_backtest_start_live(default_conf, mocker, caplog):
|
||||
default_conf['exchange']['pair_whitelist'] = ['BTC_UNITEST']
|
||||
conf = deepcopy(default_conf)
|
||||
conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
|
||||
mocker.patch('freqtrade.exchange.get_ticker_history',
|
||||
new=lambda n, i: _load_pair_as_ticks(n, i))
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock())
|
||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock())
|
||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting._generate_text_table', MagicMock())
|
||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||
read_data=json.dumps(default_conf)
|
||||
read_data=json.dumps(conf)
|
||||
))
|
||||
|
||||
args = MagicMock()
|
||||
@@ -585,7 +619,7 @@ def test_backtest_start_live(default_conf, mocker, caplog):
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'backtesting',
|
||||
'--ticker-interval', '1',
|
||||
'--ticker-interval', '1m',
|
||||
'--live',
|
||||
'--timerange', '-100'
|
||||
]
|
||||
@@ -594,7 +628,7 @@ def test_backtest_start_live(default_conf, mocker, caplog):
|
||||
# check the logs, that will contain the backtest result
|
||||
exists = [
|
||||
'Parameter -i/--ticker-interval detected ...',
|
||||
'Using ticker_interval: 1 ...',
|
||||
'Using ticker_interval: 1m ...',
|
||||
'Parameter -l/--live detected ...',
|
||||
'Using max_open_trades: 1 ...',
|
||||
'Parameter --timerange detected: -100 ..',
|
||||
|
@@ -1,20 +1,33 @@
|
||||
# pragma pylint: disable=missing-docstring,W0212,C0103
|
||||
import json
|
||||
import os
|
||||
import signal
|
||||
from copy import deepcopy
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from freqtrade.optimize.__init__ import load_tickerdata_file
|
||||
from freqtrade.optimize.hyperopt import Hyperopt, start
|
||||
from freqtrade.strategy.resolver import StrategyResolver
|
||||
from freqtrade.tests.conftest import default_conf, log_has
|
||||
from freqtrade.tests.conftest import log_has
|
||||
from freqtrade.tests.optimize.test_backtesting import get_args
|
||||
|
||||
|
||||
# Avoid to reinit the same object again and again
|
||||
_HYPEROPT = Hyperopt(default_conf())
|
||||
_HYPEROPT_INITIALIZED = False
|
||||
_HYPEROPT = None
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def init_hyperopt(default_conf, mocker):
|
||||
global _HYPEROPT_INITIALIZED, _HYPEROPT
|
||||
if not _HYPEROPT_INITIALIZED:
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf',
|
||||
MagicMock(return_value=default_conf))
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock())
|
||||
_HYPEROPT = Hyperopt(default_conf)
|
||||
_HYPEROPT_INITIALIZED = True
|
||||
|
||||
|
||||
# Functions for recurrent object patching
|
||||
@@ -51,9 +64,10 @@ def test_start(mocker, default_conf, caplog) -> None:
|
||||
"""
|
||||
start_mock = MagicMock()
|
||||
mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.start', start_mock)
|
||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||
read_data=json.dumps(default_conf)
|
||||
))
|
||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf',
|
||||
MagicMock(return_value=default_conf))
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
|
||||
|
||||
args = [
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
@@ -74,7 +88,7 @@ def test_start(mocker, default_conf, caplog) -> None:
|
||||
assert start_mock.call_count == 1
|
||||
|
||||
|
||||
def test_loss_calculation_prefer_correct_trade_count() -> None:
|
||||
def test_loss_calculation_prefer_correct_trade_count(init_hyperopt) -> None:
|
||||
"""
|
||||
Test Hyperopt.calculate_loss()
|
||||
"""
|
||||
@@ -88,7 +102,7 @@ def test_loss_calculation_prefer_correct_trade_count() -> None:
|
||||
assert under > correct
|
||||
|
||||
|
||||
def test_loss_calculation_prefer_shorter_trades() -> None:
|
||||
def test_loss_calculation_prefer_shorter_trades(init_hyperopt) -> None:
|
||||
"""
|
||||
Test Hyperopt.calculate_loss()
|
||||
"""
|
||||
@@ -99,7 +113,7 @@ def test_loss_calculation_prefer_shorter_trades() -> None:
|
||||
assert shorter < longer
|
||||
|
||||
|
||||
def test_loss_calculation_has_limited_profit() -> None:
|
||||
def test_loss_calculation_has_limited_profit(init_hyperopt) -> None:
|
||||
hyperopt = _HYPEROPT
|
||||
|
||||
correct = hyperopt.calculate_loss(hyperopt.expected_max_profit, hyperopt.target_trades, 20)
|
||||
@@ -109,7 +123,7 @@ def test_loss_calculation_has_limited_profit() -> None:
|
||||
assert under > correct
|
||||
|
||||
|
||||
def test_log_results_if_loss_improves(capsys) -> None:
|
||||
def test_log_results_if_loss_improves(init_hyperopt, capsys) -> None:
|
||||
hyperopt = _HYPEROPT
|
||||
hyperopt.current_best_loss = 2
|
||||
hyperopt.log_results(
|
||||
@@ -124,7 +138,7 @@ def test_log_results_if_loss_improves(capsys) -> None:
|
||||
assert ' 1/2: foo. Loss 1.00000'in out
|
||||
|
||||
|
||||
def test_no_log_if_loss_does_not_improve(caplog) -> None:
|
||||
def test_no_log_if_loss_does_not_improve(init_hyperopt, caplog) -> None:
|
||||
hyperopt = _HYPEROPT
|
||||
hyperopt.current_best_loss = 2
|
||||
hyperopt.log_results(
|
||||
@@ -135,7 +149,7 @@ def test_no_log_if_loss_does_not_improve(caplog) -> None:
|
||||
assert caplog.record_tuples == []
|
||||
|
||||
|
||||
def test_fmin_best_results(mocker, default_conf, caplog) -> None:
|
||||
def test_fmin_best_results(mocker, init_hyperopt, default_conf, caplog) -> None:
|
||||
fmin_result = {
|
||||
"macd_below_zero": 0,
|
||||
"adx": 1,
|
||||
@@ -169,6 +183,7 @@ def test_fmin_best_results(mocker, default_conf, caplog) -> None:
|
||||
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result)
|
||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
|
||||
|
||||
StrategyResolver({'strategy': 'DefaultStrategy'})
|
||||
hyperopt = Hyperopt(conf)
|
||||
@@ -203,7 +218,7 @@ def test_fmin_best_results(mocker, default_conf, caplog) -> None:
|
||||
assert line in caplog.text
|
||||
|
||||
|
||||
def test_fmin_throw_value_error(mocker, default_conf, caplog) -> None:
|
||||
def test_fmin_throw_value_error(mocker, init_hyperopt, default_conf, caplog) -> None:
|
||||
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||
mocker.patch('freqtrade.optimize.hyperopt.fmin', side_effect=ValueError())
|
||||
|
||||
@@ -213,6 +228,8 @@ def test_fmin_throw_value_error(mocker, default_conf, caplog) -> None:
|
||||
conf.update({'timerange': None})
|
||||
conf.update({'spaces': 'all'})
|
||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
|
||||
|
||||
StrategyResolver({'strategy': 'DefaultStrategy'})
|
||||
hyperopt = Hyperopt(conf)
|
||||
hyperopt.trials = create_trials(mocker)
|
||||
@@ -230,7 +247,7 @@ def test_fmin_throw_value_error(mocker, default_conf, caplog) -> None:
|
||||
assert line in caplog.text
|
||||
|
||||
|
||||
def test_resuming_previous_hyperopt_results_succeeds(mocker, default_conf) -> None:
|
||||
def test_resuming_previous_hyperopt_results_succeeds(mocker, init_hyperopt, default_conf) -> None:
|
||||
trials = create_trials(mocker)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
@@ -254,6 +271,7 @@ def test_resuming_previous_hyperopt_results_succeeds(mocker, default_conf) -> No
|
||||
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
|
||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock())
|
||||
|
||||
StrategyResolver({'strategy': 'DefaultStrategy'})
|
||||
hyperopt = Hyperopt(conf)
|
||||
@@ -272,7 +290,7 @@ def test_resuming_previous_hyperopt_results_succeeds(mocker, default_conf) -> No
|
||||
assert total_tries == (current_tries + len(trials.results))
|
||||
|
||||
|
||||
def test_save_trials_saves_trials(mocker, caplog) -> None:
|
||||
def test_save_trials_saves_trials(mocker, init_hyperopt, caplog) -> None:
|
||||
create_trials(mocker)
|
||||
mock_dump = mocker.patch('freqtrade.optimize.hyperopt.pickle.dump', return_value=None)
|
||||
|
||||
@@ -281,22 +299,24 @@ def test_save_trials_saves_trials(mocker, caplog) -> None:
|
||||
|
||||
hyperopt.save_trials()
|
||||
|
||||
trials_file = os.path.join('freqtrade', 'tests', 'optimize', 'ut_trials.pickle')
|
||||
assert log_has(
|
||||
'Saving Trials to \'freqtrade/tests/optimize/ut_trials.pickle\'',
|
||||
'Saving Trials to \'{}\''.format(trials_file),
|
||||
caplog.record_tuples
|
||||
)
|
||||
mock_dump.assert_called_once()
|
||||
|
||||
|
||||
def test_read_trials_returns_trials_file(mocker, caplog) -> None:
|
||||
def test_read_trials_returns_trials_file(mocker, init_hyperopt, caplog) -> None:
|
||||
trials = create_trials(mocker)
|
||||
mock_load = mocker.patch('freqtrade.optimize.hyperopt.pickle.load', return_value=trials)
|
||||
mock_open = mocker.patch('freqtrade.optimize.hyperopt.open', return_value=mock_load)
|
||||
|
||||
hyperopt = _HYPEROPT
|
||||
hyperopt_trial = hyperopt.read_trials()
|
||||
trials_file = os.path.join('freqtrade', 'tests', 'optimize', 'ut_trials.pickle')
|
||||
assert log_has(
|
||||
'Reading Trials from \'freqtrade/tests/optimize/ut_trials.pickle\'',
|
||||
'Reading Trials from \'{}\''.format(trials_file),
|
||||
caplog.record_tuples
|
||||
)
|
||||
assert hyperopt_trial == trials
|
||||
@@ -304,7 +324,7 @@ def test_read_trials_returns_trials_file(mocker, caplog) -> None:
|
||||
mock_load.assert_called_once()
|
||||
|
||||
|
||||
def test_roi_table_generation() -> None:
|
||||
def test_roi_table_generation(init_hyperopt) -> None:
|
||||
params = {
|
||||
'roi_t1': 5,
|
||||
'roi_t2': 10,
|
||||
@@ -318,10 +338,11 @@ def test_roi_table_generation() -> None:
|
||||
assert hyperopt.generate_roi_table(params) == {0: 6, 15: 3, 25: 1, 30: 0}
|
||||
|
||||
|
||||
def test_start_calls_fmin(mocker, default_conf) -> None:
|
||||
def test_start_calls_fmin(mocker, init_hyperopt, default_conf) -> None:
|
||||
trials = create_trials(mocker)
|
||||
mocker.patch('freqtrade.optimize.hyperopt.sorted', return_value=trials.results)
|
||||
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock())
|
||||
mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
@@ -339,7 +360,7 @@ def test_start_calls_fmin(mocker, default_conf) -> None:
|
||||
mock_fmin.assert_called_once()
|
||||
|
||||
|
||||
def test_start_uses_mongotrials(mocker, default_conf) -> None:
|
||||
def test_start_uses_mongotrials(mocker, init_hyperopt, default_conf) -> None:
|
||||
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||
mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
|
||||
mock_mongotrials = mocker.patch(
|
||||
@@ -354,6 +375,7 @@ def test_start_uses_mongotrials(mocker, default_conf) -> None:
|
||||
conf.update({'timerange': None})
|
||||
conf.update({'spaces': 'all'})
|
||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
|
||||
|
||||
hyperopt = Hyperopt(conf)
|
||||
hyperopt.tickerdata_to_dataframe = MagicMock()
|
||||
@@ -372,9 +394,9 @@ def test_format_results():
|
||||
Test Hyperopt.format_results()
|
||||
"""
|
||||
trades = [
|
||||
('BTC_ETH', 2, 2, 123),
|
||||
('BTC_LTC', 1, 1, 123),
|
||||
('BTC_XRP', -1, -2, -246)
|
||||
('ETH/BTC', 2, 2, 123),
|
||||
('LTC/BTC', 1, 1, 123),
|
||||
('XPR/BTC', -1, -2, -246)
|
||||
]
|
||||
labels = ['currency', 'profit_percent', 'profit_BTC', 'duration']
|
||||
df = pd.DataFrame.from_records(trades, columns=labels)
|
||||
@@ -382,7 +404,7 @@ def test_format_results():
|
||||
assert x.find(' 66.67%')
|
||||
|
||||
|
||||
def test_signal_handler(mocker):
|
||||
def test_signal_handler(mocker, init_hyperopt):
|
||||
"""
|
||||
Test Hyperopt.signal_handler()
|
||||
"""
|
||||
@@ -392,11 +414,11 @@ def test_signal_handler(mocker):
|
||||
mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.log_trials_result', m)
|
||||
|
||||
hyperopt = _HYPEROPT
|
||||
hyperopt.signal_handler(9, None)
|
||||
hyperopt.signal_handler(signal.SIGTERM, None)
|
||||
assert m.call_count == 3
|
||||
|
||||
|
||||
def test_has_space():
|
||||
def test_has_space(init_hyperopt):
|
||||
"""
|
||||
Test Hyperopt.has_space() method
|
||||
"""
|
||||
@@ -409,14 +431,14 @@ def test_has_space():
|
||||
assert _HYPEROPT.has_space('buy')
|
||||
|
||||
|
||||
def test_populate_indicators() -> None:
|
||||
def test_populate_indicators(init_hyperopt) -> None:
|
||||
"""
|
||||
Test Hyperopt.populate_indicators()
|
||||
"""
|
||||
tick = load_tickerdata_file(None, 'BTC_UNITEST', 1)
|
||||
tickerlist = {'BTC_UNITEST': tick}
|
||||
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
||||
tickerlist = {'UNITTEST/BTC': tick}
|
||||
dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist)
|
||||
dataframe = _HYPEROPT.populate_indicators(dataframes['BTC_UNITEST'])
|
||||
dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC'])
|
||||
|
||||
# Check if some indicators are generated. We will not test all of them
|
||||
assert 'adx' in dataframe
|
||||
@@ -424,14 +446,14 @@ def test_populate_indicators() -> None:
|
||||
assert 'cci' in dataframe
|
||||
|
||||
|
||||
def test_buy_strategy_generator() -> None:
|
||||
def test_buy_strategy_generator(init_hyperopt) -> None:
|
||||
"""
|
||||
Test Hyperopt.buy_strategy_generator()
|
||||
"""
|
||||
tick = load_tickerdata_file(None, 'BTC_UNITEST', 1)
|
||||
tickerlist = {'BTC_UNITEST': tick}
|
||||
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
||||
tickerlist = {'UNITTEST/BTC': tick}
|
||||
dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist)
|
||||
dataframe = _HYPEROPT.populate_indicators(dataframes['BTC_UNITEST'])
|
||||
dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC'])
|
||||
|
||||
populate_buy_trend = _HYPEROPT.buy_strategy_generator(
|
||||
{
|
||||
@@ -481,7 +503,7 @@ def test_buy_strategy_generator() -> None:
|
||||
assert 1 in result['buy']
|
||||
|
||||
|
||||
def test_generate_optimizer(mocker, default_conf) -> None:
|
||||
def test_generate_optimizer(mocker, init_hyperopt, default_conf) -> None:
|
||||
"""
|
||||
Test Hyperopt.generate_optimizer() function
|
||||
"""
|
||||
@@ -491,7 +513,7 @@ def test_generate_optimizer(mocker, default_conf) -> None:
|
||||
conf.update({'spaces': 'all'})
|
||||
|
||||
trades = [
|
||||
('BTC_POWR', 0.023117, 0.000233, 100)
|
||||
('POWR/BTC', 0.023117, 0.000233, 100)
|
||||
]
|
||||
labels = ['currency', 'profit_percent', 'profit_BTC', 'duration']
|
||||
backtest_result = pd.DataFrame.from_records(trades, columns=labels)
|
||||
@@ -500,6 +522,7 @@ def test_generate_optimizer(mocker, default_conf) -> None:
|
||||
'freqtrade.optimize.hyperopt.Hyperopt.backtest',
|
||||
MagicMock(return_value=backtest_result)
|
||||
)
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock())
|
||||
|
||||
optimizer_param = {
|
||||
'adx': {'enabled': False},
|
||||
|
@@ -3,15 +3,17 @@
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
import arrow
|
||||
from shutil import copyfile
|
||||
|
||||
from freqtrade import optimize
|
||||
from freqtrade.misc import file_dump_json
|
||||
from freqtrade.optimize.__init__ import make_testdata_path, download_pairs, \
|
||||
download_backtesting_testdata, load_tickerdata_file, trim_tickerlist
|
||||
download_backtesting_testdata, load_tickerdata_file, trim_tickerlist, \
|
||||
load_cached_data_for_updating
|
||||
from freqtrade.tests.conftest import log_has
|
||||
|
||||
# Change this if modifying BTC_UNITEST testdatafile
|
||||
# Change this if modifying UNITTEST/BTC testdatafile
|
||||
_BTC_UNITTEST_LENGTH = 13681
|
||||
|
||||
|
||||
@@ -52,11 +54,11 @@ def test_load_data_30min_ticker(ticker_history, mocker, caplog) -> None:
|
||||
"""
|
||||
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history)
|
||||
|
||||
file = 'freqtrade/tests/testdata/BTC_UNITTEST-30.json'
|
||||
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-30m.json')
|
||||
_backup_file(file, copy_file=True)
|
||||
optimize.load_data(None, pairs=['BTC_UNITTEST'], ticker_interval=30)
|
||||
optimize.load_data(None, pairs=['UNITTEST/BTC'], ticker_interval='30m')
|
||||
assert os.path.isfile(file) is True
|
||||
assert not log_has('Download the pair: "BTC_ETH", Interval: 30 min', caplog.record_tuples)
|
||||
assert not log_has('Download the pair: "UNITTEST/BTC", Interval: 30m', caplog.record_tuples)
|
||||
_clean_test_file(file)
|
||||
|
||||
|
||||
@@ -66,11 +68,11 @@ def test_load_data_5min_ticker(ticker_history, mocker, caplog) -> None:
|
||||
"""
|
||||
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history)
|
||||
|
||||
file = 'freqtrade/tests/testdata/BTC_ETH-5.json'
|
||||
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-5m.json')
|
||||
_backup_file(file, copy_file=True)
|
||||
optimize.load_data(None, pairs=['BTC_ETH'], ticker_interval=5)
|
||||
optimize.load_data(None, pairs=['UNITTEST/BTC'], ticker_interval='5m')
|
||||
assert os.path.isfile(file) is True
|
||||
assert not log_has('Download the pair: "BTC_ETH", Interval: 5 min', caplog.record_tuples)
|
||||
assert not log_has('Download the pair: "UNITTEST/BTC", Interval: 5m', caplog.record_tuples)
|
||||
_clean_test_file(file)
|
||||
|
||||
|
||||
@@ -80,11 +82,11 @@ def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None:
|
||||
"""
|
||||
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history)
|
||||
|
||||
file = 'freqtrade/tests/testdata/BTC_ETH-1.json'
|
||||
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-1m.json')
|
||||
_backup_file(file, copy_file=True)
|
||||
optimize.load_data(None, ticker_interval=1, pairs=['BTC_ETH'])
|
||||
optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC'])
|
||||
assert os.path.isfile(file) is True
|
||||
assert not log_has('Download the pair: "BTC_ETH", Interval: 1 min', caplog.record_tuples)
|
||||
assert not log_has('Download the pair: "UNITTEST/BTC", Interval: 1m', caplog.record_tuples)
|
||||
_clean_test_file(file)
|
||||
|
||||
|
||||
@@ -94,11 +96,25 @@ def test_load_data_with_new_pair_1min(ticker_history, mocker, caplog) -> None:
|
||||
"""
|
||||
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history)
|
||||
|
||||
file = 'freqtrade/tests/testdata/BTC_MEME-1.json'
|
||||
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json')
|
||||
|
||||
_backup_file(file)
|
||||
optimize.load_data(None, ticker_interval=1, pairs=['BTC_MEME'])
|
||||
# do not download a new pair if refresh_pairs isn't set
|
||||
optimize.load_data(None,
|
||||
ticker_interval='1m',
|
||||
refresh_pairs=False,
|
||||
pairs=['MEME/BTC'])
|
||||
assert os.path.isfile(file) is False
|
||||
assert log_has('No data for pair MEME/BTC, use --update-pairs-cached to download the data',
|
||||
caplog.record_tuples)
|
||||
|
||||
# download a new pair if refresh_pairs is set
|
||||
optimize.load_data(None,
|
||||
ticker_interval='1m',
|
||||
refresh_pairs=True,
|
||||
pairs=['MEME/BTC'])
|
||||
assert os.path.isfile(file) is True
|
||||
assert log_has('Download the pair: "BTC_MEME", Interval: 1 min', caplog.record_tuples)
|
||||
assert log_has('Download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples)
|
||||
_clean_test_file(file)
|
||||
|
||||
|
||||
@@ -109,10 +125,10 @@ def test_testdata_path() -> None:
|
||||
def test_download_pairs(ticker_history, mocker) -> None:
|
||||
mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history)
|
||||
|
||||
file1_1 = 'freqtrade/tests/testdata/BTC_MEME-1.json'
|
||||
file1_5 = 'freqtrade/tests/testdata/BTC_MEME-5.json'
|
||||
file2_1 = 'freqtrade/tests/testdata/BTC_CFI-1.json'
|
||||
file2_5 = 'freqtrade/tests/testdata/BTC_CFI-5.json'
|
||||
file1_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json')
|
||||
file1_5 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-5m.json')
|
||||
file2_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'CFI_BTC-1m.json')
|
||||
file2_5 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'CFI_BTC-5m.json')
|
||||
|
||||
_backup_file(file1_1)
|
||||
_backup_file(file1_5)
|
||||
@@ -122,7 +138,7 @@ def test_download_pairs(ticker_history, mocker) -> None:
|
||||
assert os.path.isfile(file1_1) is False
|
||||
assert os.path.isfile(file2_1) is False
|
||||
|
||||
assert download_pairs(None, pairs=['BTC-MEME', 'BTC-CFI'], ticker_interval=1) is True
|
||||
assert download_pairs(None, pairs=['MEME/BTC', 'CFI/BTC'], ticker_interval='1m') is True
|
||||
|
||||
assert os.path.isfile(file1_1) is True
|
||||
assert os.path.isfile(file2_1) is True
|
||||
@@ -134,7 +150,7 @@ def test_download_pairs(ticker_history, mocker) -> None:
|
||||
assert os.path.isfile(file1_5) is False
|
||||
assert os.path.isfile(file2_5) is False
|
||||
|
||||
assert download_pairs(None, pairs=['BTC-MEME', 'BTC-CFI'], ticker_interval=5) is True
|
||||
assert download_pairs(None, pairs=['MEME/BTC', 'CFI/BTC'], ticker_interval='5m') is True
|
||||
|
||||
assert os.path.isfile(file1_5) is True
|
||||
assert os.path.isfile(file2_5) is True
|
||||
@@ -144,59 +160,166 @@ def test_download_pairs(ticker_history, mocker) -> None:
|
||||
_clean_test_file(file2_5)
|
||||
|
||||
|
||||
def test_load_cached_data_for_updating(mocker) -> None:
|
||||
datadir = os.path.join(os.path.dirname(__file__), '..', 'testdata')
|
||||
|
||||
test_data = None
|
||||
test_filename = os.path.join(datadir, 'UNITTEST_BTC-1m.json')
|
||||
with open(test_filename, "rt") as file:
|
||||
test_data = json.load(file)
|
||||
|
||||
# change now time to test 'line' cases
|
||||
# now = last cached item + 1 hour
|
||||
now_ts = test_data[-1][0] / 1000 + 60 * 60
|
||||
mocker.patch('arrow.utcnow', return_value=arrow.get(now_ts))
|
||||
|
||||
# timeframe starts earlier than the cached data
|
||||
# should fully update data
|
||||
timerange = (('date', None), test_data[0][0] / 1000 - 1, None)
|
||||
data, start_ts = load_cached_data_for_updating(test_filename,
|
||||
'1m',
|
||||
timerange)
|
||||
assert data == []
|
||||
assert start_ts == test_data[0][0] - 1000
|
||||
|
||||
# same with 'line' timeframe
|
||||
num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 120
|
||||
data, start_ts = load_cached_data_for_updating(test_filename,
|
||||
'1m',
|
||||
((None, 'line'), None, -num_lines))
|
||||
assert data == []
|
||||
assert start_ts < test_data[0][0] - 1
|
||||
|
||||
# timeframe starts in the center of the cached data
|
||||
# should return the chached data w/o the last item
|
||||
timerange = (('date', None), test_data[0][0] / 1000 + 1, None)
|
||||
data, start_ts = load_cached_data_for_updating(test_filename,
|
||||
'1m',
|
||||
timerange)
|
||||
assert data == test_data[:-1]
|
||||
assert test_data[-2][0] < start_ts < test_data[-1][0]
|
||||
|
||||
# same with 'line' timeframe
|
||||
num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 30
|
||||
timerange = ((None, 'line'), None, -num_lines)
|
||||
data, start_ts = load_cached_data_for_updating(test_filename,
|
||||
'1m',
|
||||
timerange)
|
||||
assert data == test_data[:-1]
|
||||
assert test_data[-2][0] < start_ts < test_data[-1][0]
|
||||
|
||||
# timeframe starts after the chached data
|
||||
# should return the chached data w/o the last item
|
||||
timerange = (('date', None), test_data[-1][0] / 1000 + 1, None)
|
||||
data, start_ts = load_cached_data_for_updating(test_filename,
|
||||
'1m',
|
||||
timerange)
|
||||
assert data == test_data[:-1]
|
||||
assert test_data[-2][0] < start_ts < test_data[-1][0]
|
||||
|
||||
# same with 'line' timeframe
|
||||
num_lines = 30
|
||||
timerange = ((None, 'line'), None, -num_lines)
|
||||
data, start_ts = load_cached_data_for_updating(test_filename,
|
||||
'1m',
|
||||
timerange)
|
||||
assert data == test_data[:-1]
|
||||
assert test_data[-2][0] < start_ts < test_data[-1][0]
|
||||
|
||||
# no timeframe is set
|
||||
# should return the chached data w/o the last item
|
||||
num_lines = 30
|
||||
timerange = ((None, 'line'), None, -num_lines)
|
||||
data, start_ts = load_cached_data_for_updating(test_filename,
|
||||
'1m',
|
||||
timerange)
|
||||
assert data == test_data[:-1]
|
||||
assert test_data[-2][0] < start_ts < test_data[-1][0]
|
||||
|
||||
# no datafile exist
|
||||
# should return timestamp start time
|
||||
timerange = (('date', None), now_ts - 10000, None)
|
||||
data, start_ts = load_cached_data_for_updating(test_filename + 'unexist',
|
||||
'1m',
|
||||
timerange)
|
||||
assert data == []
|
||||
assert start_ts == (now_ts - 10000) * 1000
|
||||
|
||||
# same with 'line' timeframe
|
||||
num_lines = 30
|
||||
timerange = ((None, 'line'), None, -num_lines)
|
||||
data, start_ts = load_cached_data_for_updating(test_filename + 'unexist',
|
||||
'1m',
|
||||
timerange)
|
||||
assert data == []
|
||||
assert start_ts == (now_ts - num_lines * 60) * 1000
|
||||
|
||||
# no datafile exist, no timeframe is set
|
||||
# should return an empty array and None
|
||||
data, start_ts = load_cached_data_for_updating(test_filename + 'unexist',
|
||||
'1m',
|
||||
None)
|
||||
assert data == []
|
||||
assert start_ts is None
|
||||
|
||||
|
||||
def test_download_pairs_exception(ticker_history, mocker, caplog) -> None:
|
||||
mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history)
|
||||
mocker.patch('freqtrade.optimize.__init__.download_backtesting_testdata',
|
||||
side_effect=BaseException('File Error'))
|
||||
|
||||
file1_1 = 'freqtrade/tests/testdata/BTC_MEME-1.json'
|
||||
file1_5 = 'freqtrade/tests/testdata/BTC_MEME-5.json'
|
||||
file1_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json')
|
||||
file1_5 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-5m.json')
|
||||
_backup_file(file1_1)
|
||||
_backup_file(file1_5)
|
||||
|
||||
download_pairs(None, pairs=['BTC-MEME'], ticker_interval=1)
|
||||
download_pairs(None, pairs=['MEME/BTC'], ticker_interval='1m')
|
||||
# clean files freshly downloaded
|
||||
_clean_test_file(file1_1)
|
||||
_clean_test_file(file1_5)
|
||||
assert log_has('Failed to download the pair: "BTC-MEME", Interval: 1 min', caplog.record_tuples)
|
||||
assert log_has('Failed to download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples)
|
||||
|
||||
|
||||
def test_download_backtesting_testdata(ticker_history, mocker) -> None:
|
||||
mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history)
|
||||
|
||||
# Download a 1 min ticker file
|
||||
file1 = 'freqtrade/tests/testdata/BTC_XEL-1.json'
|
||||
file1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'XEL_BTC-1m.json')
|
||||
_backup_file(file1)
|
||||
download_backtesting_testdata(None, pair="BTC-XEL", interval=1)
|
||||
download_backtesting_testdata(None, pair="XEL/BTC", tick_interval='1m')
|
||||
assert os.path.isfile(file1) is True
|
||||
_clean_test_file(file1)
|
||||
|
||||
# Download a 5 min ticker file
|
||||
file2 = 'freqtrade/tests/testdata/BTC_STORJ-5.json'
|
||||
file2 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'STORJ_BTC-5m.json')
|
||||
_backup_file(file2)
|
||||
|
||||
download_backtesting_testdata(None, pair="BTC-STORJ", interval=5)
|
||||
download_backtesting_testdata(None, pair="STORJ/BTC", tick_interval='5m')
|
||||
assert os.path.isfile(file2) is True
|
||||
_clean_test_file(file2)
|
||||
|
||||
|
||||
def test_download_backtesting_testdata2(mocker) -> None:
|
||||
tick = [{'T': 'bar'}, {'T': 'foo'}]
|
||||
tick = [
|
||||
[1509836520000, 0.00162008, 0.00162008, 0.00162008, 0.00162008, 108.14853839],
|
||||
[1509836580000, 0.00161, 0.00161, 0.00161, 0.00161, 82.390199]
|
||||
]
|
||||
json_dump_mock = mocker.patch('freqtrade.misc.file_dump_json', return_value=None)
|
||||
mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=tick)
|
||||
download_backtesting_testdata(None, pair="BTC-UNITEST", interval=1)
|
||||
download_backtesting_testdata(None, pair="BTC-UNITEST", interval=3)
|
||||
|
||||
download_backtesting_testdata(None, pair="UNITTEST/BTC", tick_interval='1m')
|
||||
download_backtesting_testdata(None, pair="UNITTEST/BTC", tick_interval='3m')
|
||||
assert json_dump_mock.call_count == 2
|
||||
|
||||
|
||||
def test_load_tickerdata_file() -> None:
|
||||
# 7 does not exist in either format.
|
||||
assert not load_tickerdata_file(None, 'BTC_UNITEST', 7)
|
||||
assert not load_tickerdata_file(None, 'UNITTEST/BTC', '7m')
|
||||
# 1 exists only as a .json
|
||||
tickerdata = load_tickerdata_file(None, 'BTC_UNITEST', 1)
|
||||
tickerdata = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
||||
assert _BTC_UNITTEST_LENGTH == len(tickerdata)
|
||||
# 8 .json is empty and will fail if it's loaded. .json.gz is a copy of 1.json
|
||||
tickerdata = load_tickerdata_file(None, 'BTC_UNITEST', 8)
|
||||
tickerdata = load_tickerdata_file(None, 'UNITTEST/BTC', '8m')
|
||||
assert _BTC_UNITTEST_LENGTH == len(tickerdata)
|
||||
|
||||
|
||||
@@ -207,22 +330,23 @@ def test_init(default_conf, mocker) -> None:
|
||||
'',
|
||||
pairs=[],
|
||||
refresh_pairs=True,
|
||||
ticker_interval=int(default_conf['ticker_interval'])
|
||||
ticker_interval=default_conf['ticker_interval']
|
||||
)
|
||||
|
||||
|
||||
def test_trim_tickerlist() -> None:
|
||||
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
|
||||
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-1m.json')
|
||||
with open(file) as data_file:
|
||||
ticker_list = json.load(data_file)
|
||||
ticker_list_len = len(ticker_list)
|
||||
|
||||
# Test the pattern ^(-\d+)$
|
||||
# This pattern remove X element from the beginning
|
||||
timerange = ((None, 'line'), None, 5)
|
||||
# This pattern uses the latest N elements
|
||||
timerange = ((None, 'line'), None, -5)
|
||||
ticker = trim_tickerlist(ticker_list, timerange)
|
||||
ticker_len = len(ticker)
|
||||
|
||||
assert ticker_list_len == ticker_len + 5
|
||||
assert ticker_len == 5
|
||||
assert ticker_list[0] is not ticker[0] # The first element should be different
|
||||
assert ticker_list[-1] is ticker[-1] # The last element must be the same
|
||||
|
||||
@@ -247,6 +371,37 @@ def test_trim_tickerlist() -> None:
|
||||
assert ticker_list[5] is ticker[0] # The list starts at the index 5
|
||||
assert ticker_list[9] is ticker[-1] # The list ends at the index 9 (5 elements)
|
||||
|
||||
# Test the pattern ^(\d{8})-(\d{8})$
|
||||
# This pattern extract a window between the dates
|
||||
timerange = (('date', 'date'), ticker_list[5][0] / 1000, ticker_list[10][0] / 1000 - 1)
|
||||
ticker = trim_tickerlist(ticker_list, timerange)
|
||||
ticker_len = len(ticker)
|
||||
|
||||
assert ticker_len == 5
|
||||
assert ticker_list[0] is not ticker[0] # The first element should be different
|
||||
assert ticker_list[5] is ticker[0] # The list starts at the index 5
|
||||
assert ticker_list[9] is ticker[-1] # The list ends at the index 9 (5 elements)
|
||||
|
||||
# Test the pattern ^-(\d{8})$
|
||||
# This pattern extracts elements from the start to the date
|
||||
timerange = ((None, 'date'), None, ticker_list[10][0] / 1000 - 1)
|
||||
ticker = trim_tickerlist(ticker_list, timerange)
|
||||
ticker_len = len(ticker)
|
||||
|
||||
assert ticker_len == 10
|
||||
assert ticker_list[0] is ticker[0] # The start of the list is included
|
||||
assert ticker_list[9] is ticker[-1] # The element 10 is not included
|
||||
|
||||
# Test the pattern ^(\d{8})-$
|
||||
# This pattern extracts elements from the date to now
|
||||
timerange = (('date', None), ticker_list[10][0] / 1000 - 1, None)
|
||||
ticker = trim_tickerlist(ticker_list, timerange)
|
||||
ticker_len = len(ticker)
|
||||
|
||||
assert ticker_len == ticker_list_len - 10
|
||||
assert ticker_list[10] is ticker[0] # The first element is element #10
|
||||
assert ticker_list[-1] is ticker[-1] # The last element is the same
|
||||
|
||||
# Test a wrong pattern
|
||||
# This pattern must return the list unchanged
|
||||
timerange = ((None, None), None, 5)
|
||||
@@ -261,7 +416,8 @@ def test_file_dump_json() -> None:
|
||||
Test file_dump_json()
|
||||
:return: None
|
||||
"""
|
||||
file = 'freqtrade/tests/testdata/test_{id}.json'.format(id=str(uuid.uuid4()))
|
||||
file = os.path.join(os.path.dirname(__file__), '..', 'testdata',
|
||||
'test_{id}.json'.format(id=str(uuid.uuid4())))
|
||||
data = {'bar': 'foo'}
|
||||
|
||||
# check the file we will create does not exist
|
||||
|
@@ -25,7 +25,7 @@ def prec_satoshi(a, b) -> float:
|
||||
|
||||
|
||||
# Unit tests
|
||||
def test_rpc_trade_status(default_conf, ticker, mocker) -> None:
|
||||
def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
"""
|
||||
Test rpc_trade_status() method
|
||||
"""
|
||||
@@ -35,7 +35,8 @@ def test_rpc_trade_status(default_conf, ticker, mocker) -> None:
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@@ -59,7 +60,7 @@ def test_rpc_trade_status(default_conf, ticker, mocker) -> None:
|
||||
result_message = [
|
||||
'*Trade ID:* `1`\n'
|
||||
'*Current Pair:* '
|
||||
'[BTC_ETH](https://www.bittrex.com/Market/Index?MarketName=BTC-ETH)\n'
|
||||
'[ETH/BTC](https://bittrex.com/Market/Index?MarketName=BTC-ETH)\n'
|
||||
'*Open Since:* `just now`\n'
|
||||
'*Amount:* `90.99181074`\n'
|
||||
'*Open Rate:* `0.00001099`\n'
|
||||
@@ -67,13 +68,13 @@ def test_rpc_trade_status(default_conf, ticker, mocker) -> None:
|
||||
'*Current Rate:* `0.00001098`\n'
|
||||
'*Close Profit:* `None`\n'
|
||||
'*Current Profit:* `-0.59%`\n'
|
||||
'*Open Order:* `(LIMIT_BUY rem=0.00000000)`'
|
||||
'*Open Order:* `(limit buy rem=0.00000000)`'
|
||||
]
|
||||
assert result == result_message
|
||||
assert trade.find('[BTC_ETH]') >= 0
|
||||
assert trade.find('[ETH/BTC]') >= 0
|
||||
|
||||
|
||||
def test_rpc_status_table(default_conf, ticker, mocker) -> None:
|
||||
def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||
"""
|
||||
Test rpc_status_table() method
|
||||
"""
|
||||
@@ -83,7 +84,8 @@ def test_rpc_status_table(default_conf, ticker, mocker) -> None:
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@@ -102,13 +104,13 @@ def test_rpc_status_table(default_conf, ticker, mocker) -> None:
|
||||
freqtradebot.create_trade()
|
||||
(error, result) = rpc.rpc_status_table()
|
||||
assert 'just now' in result['Since'].all()
|
||||
assert 'BTC_ETH' in result['Pair'].all()
|
||||
assert 'ETH/BTC' in result['Pair'].all()
|
||||
assert '-0.59%' in result['Profit'].all()
|
||||
assert 'Value' in result
|
||||
|
||||
|
||||
def test_rpc_daily_profit(default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker)\
|
||||
-> None:
|
||||
def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
||||
limit_buy_order, limit_sell_order, mocker) -> None:
|
||||
"""
|
||||
Test rpc_daily_profit() method
|
||||
"""
|
||||
@@ -118,7 +120,8 @@ def test_rpc_daily_profit(default_conf, update, ticker, limit_buy_order, limit_s
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@@ -159,8 +162,8 @@ def test_rpc_daily_profit(default_conf, update, ticker, limit_buy_order, limit_s
|
||||
assert days.find('must be an integer greater than 0') >= 0
|
||||
|
||||
|
||||
def test_rpc_trade_statistics(
|
||||
default_conf, ticker, ticker_sell_up, limit_buy_order, limit_sell_order, mocker) -> None:
|
||||
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
||||
limit_buy_order, limit_sell_order, mocker) -> None:
|
||||
"""
|
||||
Test rpc_trade_statistics() method
|
||||
"""
|
||||
@@ -174,7 +177,8 @@ def test_rpc_trade_statistics(
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@@ -215,14 +219,14 @@ def test_rpc_trade_statistics(
|
||||
assert stats['first_trade_date'] == 'just now'
|
||||
assert stats['latest_trade_date'] == 'just now'
|
||||
assert stats['avg_duration'] == '0:00:00'
|
||||
assert stats['best_pair'] == 'BTC_ETH'
|
||||
assert stats['best_pair'] == 'ETH/BTC'
|
||||
assert prec_satoshi(stats['best_rate'], 6.2)
|
||||
|
||||
|
||||
# Test that rpc_trade_statistics can handle trades that lacks
|
||||
# trade.open_rate (it is set to None)
|
||||
def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, ticker_sell_up, limit_buy_order,
|
||||
limit_sell_order):
|
||||
def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee,
|
||||
ticker_sell_up, limit_buy_order, limit_sell_order):
|
||||
"""
|
||||
Test rpc_trade_statistics() method
|
||||
"""
|
||||
@@ -236,7 +240,8 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, ticker_sell_u
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@@ -254,7 +259,8 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, ticker_sell_u
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker_sell_up
|
||||
get_ticker=ticker_sell_up,
|
||||
get_fee=fee
|
||||
)
|
||||
trade.update(limit_sell_order)
|
||||
trade.close_date = datetime.utcnow()
|
||||
@@ -275,7 +281,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, ticker_sell_u
|
||||
assert stats['first_trade_date'] == 'just now'
|
||||
assert stats['latest_trade_date'] == 'just now'
|
||||
assert stats['avg_duration'] == '0:00:00'
|
||||
assert stats['best_pair'] == 'BTC_ETH'
|
||||
assert stats['best_pair'] == 'ETH/BTC'
|
||||
assert prec_satoshi(stats['best_rate'], 6.2)
|
||||
|
||||
|
||||
@@ -283,22 +289,18 @@ def test_rpc_balance_handle(default_conf, mocker):
|
||||
"""
|
||||
Test rpc_balance() method
|
||||
"""
|
||||
mock_balance = [
|
||||
{
|
||||
'Currency': 'BTC',
|
||||
'Balance': 10.0,
|
||||
'Available': 12.0,
|
||||
'Pending': 0.0,
|
||||
'CryptoAddress': 'XXXX',
|
||||
mock_balance = {
|
||||
'BTC': {
|
||||
'free': 10.0,
|
||||
'total': 12.0,
|
||||
'used': 2.0,
|
||||
},
|
||||
{
|
||||
'Currency': 'ETH',
|
||||
'Balance': 0.0,
|
||||
'Available': 0.0,
|
||||
'Pending': 0.0,
|
||||
'CryptoAddress': 'XXXX',
|
||||
'ETH': {
|
||||
'free': 0.0,
|
||||
'total': 0.0,
|
||||
'used': 0.0,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
patch_get_signal(mocker, (True, False))
|
||||
mocker.patch.multiple(
|
||||
@@ -319,15 +321,15 @@ def test_rpc_balance_handle(default_conf, mocker):
|
||||
(error, res) = rpc.rpc_balance(default_conf['fiat_display_currency'])
|
||||
assert not error
|
||||
(trade, x, y, z) = res
|
||||
assert prec_satoshi(x, 10)
|
||||
assert prec_satoshi(z, 150000)
|
||||
assert prec_satoshi(x, 12)
|
||||
assert prec_satoshi(z, 180000)
|
||||
assert 'USD' in y
|
||||
assert len(trade) == 1
|
||||
assert 'BTC' in trade[0]['currency']
|
||||
assert prec_satoshi(trade[0]['available'], 12)
|
||||
assert prec_satoshi(trade[0]['balance'], 10)
|
||||
assert prec_satoshi(trade[0]['pending'], 0)
|
||||
assert prec_satoshi(trade[0]['est_btc'], 10)
|
||||
assert prec_satoshi(trade[0]['available'], 10)
|
||||
assert prec_satoshi(trade[0]['balance'], 12)
|
||||
assert prec_satoshi(trade[0]['pending'], 2)
|
||||
assert prec_satoshi(trade[0]['est_btc'], 12)
|
||||
|
||||
|
||||
def test_rpc_start(mocker, default_conf) -> None:
|
||||
@@ -386,7 +388,7 @@ def test_rpc_stop(mocker, default_conf) -> None:
|
||||
assert freqtradebot.state == State.STOPPED
|
||||
|
||||
|
||||
def test_rpc_forcesell(default_conf, ticker, mocker) -> None:
|
||||
def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
|
||||
"""
|
||||
Test rpc_forcesell() method
|
||||
"""
|
||||
@@ -402,10 +404,12 @@ def test_rpc_forcesell(default_conf, ticker, mocker) -> None:
|
||||
cancel_order=cancel_order_mock,
|
||||
get_order=MagicMock(
|
||||
return_value={
|
||||
'closed': True,
|
||||
'type': 'LIMIT_BUY',
|
||||
'status': 'closed',
|
||||
'type': 'limit',
|
||||
'side': 'buy'
|
||||
}
|
||||
)
|
||||
),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@@ -449,8 +453,9 @@ def test_rpc_forcesell(default_conf, ticker, mocker) -> None:
|
||||
mocker.patch(
|
||||
'freqtrade.freqtradebot.exchange.get_order',
|
||||
return_value={
|
||||
'closed': None,
|
||||
'type': 'LIMIT_BUY'
|
||||
'status': 'open',
|
||||
'type': 'limit',
|
||||
'side': 'buy'
|
||||
}
|
||||
)
|
||||
# check that the trade is called, which is done
|
||||
@@ -465,8 +470,9 @@ def test_rpc_forcesell(default_conf, ticker, mocker) -> None:
|
||||
mocker.patch(
|
||||
'freqtrade.freqtradebot.exchange.get_order',
|
||||
return_value={
|
||||
'closed': None,
|
||||
'type': 'LIMIT_SELL'
|
||||
'status': 'open',
|
||||
'type': 'limit',
|
||||
'side': 'sell'
|
||||
}
|
||||
)
|
||||
(error, res) = rpc.rpc_forcesell('2')
|
||||
@@ -476,7 +482,7 @@ def test_rpc_forcesell(default_conf, ticker, mocker) -> None:
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
|
||||
def test_performance_handle(default_conf, ticker, limit_buy_order,
|
||||
def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
|
||||
limit_sell_order, mocker) -> None:
|
||||
"""
|
||||
Test rpc_performance() method
|
||||
@@ -488,7 +494,8 @@ def test_performance_handle(default_conf, ticker, limit_buy_order,
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@@ -510,12 +517,12 @@ def test_performance_handle(default_conf, ticker, limit_buy_order,
|
||||
(error, res) = rpc.rpc_performance()
|
||||
assert not error
|
||||
assert len(res) == 1
|
||||
assert res[0]['pair'] == 'BTC_ETH'
|
||||
assert res[0]['pair'] == 'ETH/BTC'
|
||||
assert res[0]['count'] == 1
|
||||
assert prec_satoshi(res[0]['profit'], 6.2)
|
||||
|
||||
|
||||
def test_rpc_count(mocker, default_conf, ticker) -> None:
|
||||
def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
|
||||
"""
|
||||
Test rpc_count() method
|
||||
"""
|
||||
@@ -526,7 +533,8 @@ def test_rpc_count(mocker, default_conf, ticker) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
@@ -234,7 +234,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_status(default_conf, update, mocker, ticker) -> None:
|
||||
def test_status(default_conf, update, mocker, fee, ticker) -> None:
|
||||
"""
|
||||
Test _status() method
|
||||
"""
|
||||
@@ -248,7 +248,9 @@ def test_status(default_conf, update, mocker, ticker) -> None:
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_pair_detail_url=MagicMock(),
|
||||
get_fee=fee,
|
||||
)
|
||||
msg_mock = MagicMock()
|
||||
status_table = MagicMock()
|
||||
@@ -277,7 +279,7 @@ def test_status(default_conf, update, mocker, ticker) -> None:
|
||||
assert status_table.call_count == 1
|
||||
|
||||
|
||||
def test_status_handle(default_conf, update, ticker, mocker) -> None:
|
||||
def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
|
||||
"""
|
||||
Test _status() method
|
||||
"""
|
||||
@@ -286,7 +288,8 @@ def test_status_handle(default_conf, update, ticker, mocker) -> None:
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
)
|
||||
msg_mock = MagicMock()
|
||||
status_table = MagicMock()
|
||||
@@ -319,10 +322,10 @@ def test_status_handle(default_conf, update, ticker, mocker) -> None:
|
||||
telegram._status(bot=MagicMock(), update=update)
|
||||
|
||||
assert msg_mock.call_count == 1
|
||||
assert '[BTC_ETH]' in msg_mock.call_args_list[0][0][0]
|
||||
assert '[ETH/BTC]' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_status_table_handle(default_conf, update, ticker, mocker) -> None:
|
||||
def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
|
||||
"""
|
||||
Test _status_table() method
|
||||
"""
|
||||
@@ -332,7 +335,8 @@ def test_status_table_handle(default_conf, update, ticker, mocker) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_order_id')
|
||||
buy=MagicMock(return_value={'id': 'mocked_order_id'}),
|
||||
get_fee=fee,
|
||||
)
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
@@ -369,11 +373,11 @@ def test_status_table_handle(default_conf, update, ticker, mocker) -> None:
|
||||
fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ')
|
||||
|
||||
assert int(fields[0]) == 1
|
||||
assert fields[1] == 'BTC_ETH'
|
||||
assert fields[1] == 'ETH/BTC'
|
||||
assert msg_mock.call_count == 1
|
||||
|
||||
|
||||
def test_daily_handle(default_conf, update, ticker, limit_buy_order,
|
||||
def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
|
||||
limit_sell_order, mocker) -> None:
|
||||
"""
|
||||
Test _daily() method
|
||||
@@ -387,7 +391,8 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
@@ -484,7 +489,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
|
||||
assert str('Daily Profit over the last 7 days') in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_profit_handle(default_conf, update, ticker, ticker_sell_up,
|
||||
def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
||||
limit_buy_order, limit_sell_order, mocker) -> None:
|
||||
"""
|
||||
Test _profit() method
|
||||
@@ -495,7 +500,8 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
@@ -541,49 +547,42 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up,
|
||||
assert '∙ `0.00006217 BTC (6.20%)`' in msg_mock.call_args_list[-1][0][0]
|
||||
assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0]
|
||||
|
||||
assert '*Best Performing:* `BTC_ETH: 6.20%`' in msg_mock.call_args_list[-1][0][0]
|
||||
assert '*Best Performing:* `ETH/BTC: 6.20%`' in msg_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
||||
def test_telegram_balance_handle(default_conf, update, mocker) -> None:
|
||||
"""
|
||||
Test _balance() method
|
||||
"""
|
||||
mock_balance = [
|
||||
{
|
||||
'Currency': 'BTC',
|
||||
'Balance': 10.0,
|
||||
'Available': 12.0,
|
||||
'Pending': 0.0,
|
||||
'CryptoAddress': 'XXXX',
|
||||
|
||||
mock_balance = {
|
||||
'BTC': {
|
||||
'total': 12.0,
|
||||
'free': 12.0,
|
||||
'used': 0.0
|
||||
},
|
||||
{
|
||||
'Currency': 'ETH',
|
||||
'Balance': 0.0,
|
||||
'Available': 0.0,
|
||||
'Pending': 0.0,
|
||||
'CryptoAddress': 'XXXX',
|
||||
'ETH': {
|
||||
'total': 0.0,
|
||||
'free': 0.0,
|
||||
'used': 0.0
|
||||
},
|
||||
{
|
||||
'Currency': 'USDT',
|
||||
'Balance': 10000.0,
|
||||
'Available': 0.0,
|
||||
'Pending': 0.0,
|
||||
'CryptoAddress': 'XXXX',
|
||||
'USDT': {
|
||||
'total': 10000.0,
|
||||
'free': 10000.0,
|
||||
'used': 0.0
|
||||
},
|
||||
{
|
||||
'Currency': 'LTC',
|
||||
'Balance': 10.0,
|
||||
'Available': 10.0,
|
||||
'Pending': 0.0,
|
||||
'CryptoAddress': 'XXXX',
|
||||
'LTC': {
|
||||
'total': 10.0,
|
||||
'free': 10.0,
|
||||
'used': 0.0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def mock_ticker(symbol, refresh):
|
||||
"""
|
||||
Mock Bittrex.get_ticker() response
|
||||
"""
|
||||
if symbol == 'USDT_BTC':
|
||||
if symbol == 'BTC/USDT':
|
||||
return {
|
||||
'bid': 10000.00,
|
||||
'ask': 10000.00,
|
||||
@@ -615,12 +614,12 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
|
||||
telegram._balance(bot=MagicMock(), update=update)
|
||||
result = msg_mock.call_args_list[0][0][0]
|
||||
assert msg_mock.call_count == 1
|
||||
assert '*Currency*: BTC' in result
|
||||
assert '*Currency*: ETH' not in result
|
||||
assert '*Currency*: USDT' in result
|
||||
assert 'Balance' in result
|
||||
assert 'Est. BTC' in result
|
||||
assert '*BTC*: 12.00000000' in result
|
||||
assert '*BTC:*' in result
|
||||
assert '*ETH:*' not in result
|
||||
assert '*USDT:*' in result
|
||||
assert 'Balance:' in result
|
||||
assert 'Est. BTC:' in result
|
||||
assert 'BTC: 14.00000000' in result
|
||||
|
||||
|
||||
def test_zero_balance_handle(default_conf, update, mocker) -> None:
|
||||
@@ -630,7 +629,7 @@ def test_zero_balance_handle(default_conf, update, mocker) -> None:
|
||||
patch_get_signal(mocker, (True, False))
|
||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock())
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.get_balances', return_value=[])
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.get_balances', return_value={})
|
||||
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
@@ -747,7 +746,7 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None:
|
||||
assert 'already stopped' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker) -> None:
|
||||
def test_forcesell_handle(default_conf, update, ticker, fee, ticker_sell_up, mocker) -> None:
|
||||
"""
|
||||
Test _forcesell() method
|
||||
"""
|
||||
@@ -759,7 +758,8 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@@ -779,14 +779,14 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker)
|
||||
|
||||
assert rpc_mock.call_count == 2
|
||||
assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert 'profit: 6.11%, 0.00006126' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '0.919 USD' in rpc_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
||||
def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, mocker) -> None:
|
||||
def test_forcesell_down_handle(default_conf, update, ticker, fee, ticker_sell_down, mocker) -> None:
|
||||
"""
|
||||
Test _forcesell() method
|
||||
"""
|
||||
@@ -798,7 +798,8 @@ def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, m
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@@ -822,14 +823,14 @@ def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, m
|
||||
|
||||
assert rpc_mock.call_count == 2
|
||||
assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '0.00001044' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '-0.824 USD' in rpc_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
||||
def test_forcesell_all_handle(default_conf, update, ticker, mocker) -> None:
|
||||
def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None:
|
||||
"""
|
||||
Test _forcesell() method
|
||||
"""
|
||||
@@ -838,10 +839,12 @@ def test_forcesell_all_handle(default_conf, update, ticker, mocker) -> None:
|
||||
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||
mocker.patch('freqtrade.exchange.get_pair_detail_url', MagicMock())
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@@ -904,8 +907,8 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
|
||||
assert 'Invalid argument.' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_performance_handle(default_conf, update, ticker, limit_buy_order,
|
||||
limit_sell_order, mocker) -> None:
|
||||
def test_performance_handle(default_conf, update, ticker, fee,
|
||||
limit_buy_order, limit_sell_order, mocker) -> None:
|
||||
"""
|
||||
Test _performance() method
|
||||
"""
|
||||
@@ -920,7 +923,8 @@ def test_performance_handle(default_conf, update, ticker, limit_buy_order,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@@ -942,7 +946,7 @@ def test_performance_handle(default_conf, update, ticker, limit_buy_order,
|
||||
telegram._performance(bot=MagicMock(), update=update)
|
||||
assert msg_mock.call_count == 1
|
||||
assert 'Performance' in msg_mock.call_args_list[0][0][0]
|
||||
assert '<code>BTC_ETH\t6.20% (1)</code>' in msg_mock.call_args_list[0][0][0]
|
||||
assert '<code>ETH/BTC\t6.20% (1)</code>' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_performance_handle_invalid(default_conf, update, mocker) -> None:
|
||||
@@ -968,7 +972,7 @@ def test_performance_handle_invalid(default_conf, update, mocker) -> None:
|
||||
assert 'not running' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_count_handle(default_conf, update, ticker, mocker) -> None:
|
||||
def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
|
||||
"""
|
||||
Test _count() method
|
||||
"""
|
||||
@@ -984,8 +988,9 @@ def test_count_handle(default_conf, update, ticker, mocker) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_order_id')
|
||||
buy=MagicMock(return_value={'id': 'mocked_order_id'})
|
||||
)
|
||||
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
telegram = Telegram(freqtradebot)
|
||||
|
||||
|
@@ -9,7 +9,7 @@ from freqtrade.strategy.default_strategy import DefaultStrategy
|
||||
|
||||
@pytest.fixture
|
||||
def result():
|
||||
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
|
||||
with open('freqtrade/tests/testdata/ETH_BTC-1m.json') as data_file:
|
||||
return Analyze.parse_ticker_dataframe(json.load(data_file))
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ def test_default_strategy(result):
|
||||
|
||||
assert type(strategy.minimal_roi) is dict
|
||||
assert type(strategy.stoploss) is float
|
||||
assert type(strategy.ticker_interval) is int
|
||||
assert type(strategy.ticker_interval) is str
|
||||
indicators = strategy.populate_indicators(result)
|
||||
assert type(indicators) is DataFrame
|
||||
assert type(strategy.populate_buy_trend(indicators)) is DataFrame
|
||||
|
@@ -1,6 +1,7 @@
|
||||
# pragma pylint: disable=missing-docstring,C0103,protected-access
|
||||
|
||||
import freqtrade.tests.conftest as tt # test tools
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
# whitelist, blacklist, filtering, all of that will
|
||||
# eventually become some rules to run on a generic ACL engine
|
||||
@@ -12,118 +13,60 @@ def whitelist_conf():
|
||||
|
||||
config['stake_currency'] = 'BTC'
|
||||
config['exchange']['pair_whitelist'] = [
|
||||
'BTC_ETH',
|
||||
'BTC_TKN',
|
||||
'BTC_TRST',
|
||||
'BTC_SWT',
|
||||
'BTC_BCC'
|
||||
'ETH/BTC',
|
||||
'TKN/BTC',
|
||||
'TRST/BTC',
|
||||
'SWT/BTC',
|
||||
'BCC/BTC'
|
||||
]
|
||||
|
||||
config['exchange']['pair_blacklist'] = [
|
||||
'BTC_BLK'
|
||||
'BLK/BTC'
|
||||
]
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def get_market_summaries():
|
||||
return [{
|
||||
'MarketName': 'BTC-TKN',
|
||||
'High': 0.00000919,
|
||||
'Low': 0.00000820,
|
||||
'Volume': 74339.61396015,
|
||||
'Last': 0.00000820,
|
||||
'BaseVolume': 1664,
|
||||
'TimeStamp': '2014-07-09T07:19:30.15',
|
||||
'Bid': 0.00000820,
|
||||
'Ask': 0.00000831,
|
||||
'OpenBuyOrders': 15,
|
||||
'OpenSellOrders': 15,
|
||||
'PrevDay': 0.00000821,
|
||||
'Created': '2014-03-20T06:00:00',
|
||||
'DisplayMarketName': ''
|
||||
}, {
|
||||
'MarketName': 'BTC-ETH',
|
||||
'High': 0.00000072,
|
||||
'Low': 0.00000001,
|
||||
'Volume': 166340678.42280999,
|
||||
'Last': 0.00000005,
|
||||
'BaseVolume': 42,
|
||||
'TimeStamp': '2014-07-09T07:21:40.51',
|
||||
'Bid': 0.00000004,
|
||||
'Ask': 0.00000005,
|
||||
'OpenBuyOrders': 18,
|
||||
'OpenSellOrders': 18,
|
||||
'PrevDay': 0.00000002,
|
||||
'Created': '2014-05-30T07:57:49.637',
|
||||
'DisplayMarketName': ''
|
||||
}, {
|
||||
'MarketName': 'BTC-BLK',
|
||||
'High': 0.00000072,
|
||||
'Low': 0.00000001,
|
||||
'Volume': 166340678.42280999,
|
||||
'Last': 0.00000005,
|
||||
'BaseVolume': 3,
|
||||
'TimeStamp': '2014-07-09T07:21:40.51',
|
||||
'Bid': 0.00000004,
|
||||
'Ask': 0.00000005,
|
||||
'OpenBuyOrders': 18,
|
||||
'OpenSellOrders': 18,
|
||||
'PrevDay': 0.00000002,
|
||||
'Created': '2014-05-30T07:57:49.637',
|
||||
'DisplayMarketName': ''
|
||||
}]
|
||||
|
||||
|
||||
def get_health():
|
||||
return [{'Currency': 'ETH', 'IsActive': True},
|
||||
{'Currency': 'TKN', 'IsActive': True},
|
||||
{'Currency': 'BLK', 'IsActive': True}]
|
||||
|
||||
|
||||
def get_health_empty():
|
||||
return []
|
||||
|
||||
|
||||
def test_refresh_market_pair_not_in_whitelist(mocker):
|
||||
def test_refresh_market_pair_not_in_whitelist(mocker, markets):
|
||||
conf = whitelist_conf()
|
||||
|
||||
freqtradebot = tt.get_patched_freqtradebot(mocker, conf)
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.get_wallet_health', get_health)
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.get_markets', markets)
|
||||
refreshedwhitelist = freqtradebot._refresh_whitelist(
|
||||
conf['exchange']['pair_whitelist'] + ['BTC_XXX']
|
||||
conf['exchange']['pair_whitelist'] + ['XXX/BTC']
|
||||
)
|
||||
# List ordered by BaseVolume
|
||||
whitelist = ['BTC_ETH', 'BTC_TKN']
|
||||
whitelist = ['ETH/BTC', 'TKN/BTC']
|
||||
# Ensure all except those in whitelist are removed
|
||||
assert whitelist == refreshedwhitelist
|
||||
|
||||
|
||||
def test_refresh_whitelist(mocker):
|
||||
def test_refresh_whitelist(mocker, markets):
|
||||
conf = whitelist_conf()
|
||||
freqtradebot = tt.get_patched_freqtradebot(mocker, conf)
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.get_wallet_health', get_health)
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.get_markets', markets)
|
||||
refreshedwhitelist = freqtradebot._refresh_whitelist(conf['exchange']['pair_whitelist'])
|
||||
|
||||
# List ordered by BaseVolume
|
||||
whitelist = ['BTC_ETH', 'BTC_TKN']
|
||||
whitelist = ['ETH/BTC', 'TKN/BTC']
|
||||
# Ensure all except those in whitelist are removed
|
||||
assert whitelist == refreshedwhitelist
|
||||
|
||||
|
||||
def test_refresh_whitelist_dynamic(mocker):
|
||||
def test_refresh_whitelist_dynamic(mocker, markets, tickers):
|
||||
conf = whitelist_conf()
|
||||
freqtradebot = tt.get_patched_freqtradebot(mocker, conf)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
get_wallet_health=get_health,
|
||||
get_market_summaries=get_market_summaries
|
||||
get_markets=markets,
|
||||
get_tickers=tickers,
|
||||
exchange_has=MagicMock(return_value=True)
|
||||
)
|
||||
|
||||
# argument: use the whitelist dynamically by exchange-volume
|
||||
whitelist = ['BTC_TKN', 'BTC_ETH']
|
||||
whitelist = ['ETH/BTC', 'TKN/BTC']
|
||||
|
||||
refreshedwhitelist = freqtradebot._refresh_whitelist(
|
||||
freqtradebot._gen_pair_whitelist(conf['stake_currency'])
|
||||
@@ -132,10 +75,10 @@ def test_refresh_whitelist_dynamic(mocker):
|
||||
assert whitelist == refreshedwhitelist
|
||||
|
||||
|
||||
def test_refresh_whitelist_dynamic_empty(mocker):
|
||||
def test_refresh_whitelist_dynamic_empty(mocker, markets_empty):
|
||||
conf = whitelist_conf()
|
||||
freqtradebot = tt.get_patched_freqtradebot(mocker, conf)
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.get_wallet_health', get_health_empty)
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.get_markets', markets_empty)
|
||||
|
||||
# argument: use the whitelist dynamically by exchange-volume
|
||||
whitelist = []
|
||||
|
@@ -50,7 +50,7 @@ def test_dataframe_correct_length(result):
|
||||
|
||||
def test_dataframe_correct_columns(result):
|
||||
assert result.columns.tolist() == \
|
||||
['date', 'close', 'high', 'low', 'open', 'volume']
|
||||
['date', 'open', 'high', 'low', 'close', 'volume']
|
||||
|
||||
|
||||
def test_populates_buy_trend(result):
|
||||
@@ -74,7 +74,7 @@ def test_returns_latest_buy_signal(mocker):
|
||||
return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}])
|
||||
)
|
||||
)
|
||||
assert _ANALYZE.get_signal('BTC-ETH', 5) == (True, False)
|
||||
assert _ANALYZE.get_signal('ETH/BTC', '5m') == (True, False)
|
||||
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.analyze.Analyze',
|
||||
@@ -82,7 +82,7 @@ def test_returns_latest_buy_signal(mocker):
|
||||
return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}])
|
||||
)
|
||||
)
|
||||
assert _ANALYZE.get_signal('BTC-ETH', 5) == (False, True)
|
||||
assert _ANALYZE.get_signal('ETH/BTC', '5m') == (False, True)
|
||||
|
||||
|
||||
def test_returns_latest_sell_signal(mocker):
|
||||
@@ -94,7 +94,7 @@ def test_returns_latest_sell_signal(mocker):
|
||||
)
|
||||
)
|
||||
|
||||
assert _ANALYZE.get_signal('BTC-ETH', 5) == (False, True)
|
||||
assert _ANALYZE.get_signal('ETH/BTC', '5m') == (False, True)
|
||||
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.analyze.Analyze',
|
||||
@@ -102,13 +102,13 @@ def test_returns_latest_sell_signal(mocker):
|
||||
return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}])
|
||||
)
|
||||
)
|
||||
assert _ANALYZE.get_signal('BTC-ETH', 5) == (True, False)
|
||||
assert _ANALYZE.get_signal('ETH/BTC', '5m') == (True, False)
|
||||
|
||||
|
||||
def test_get_signal_empty(default_conf, mocker, caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=None)
|
||||
assert (False, False) == _ANALYZE.get_signal('foo', int(default_conf['ticker_interval']))
|
||||
assert (False, False) == _ANALYZE.get_signal('foo', default_conf['ticker_interval'])
|
||||
assert log_has('Empty ticker history for pair foo', caplog.record_tuples)
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ def test_get_signal_exception_valueerror(default_conf, mocker, caplog):
|
||||
side_effect=ValueError('xyz')
|
||||
)
|
||||
)
|
||||
assert (False, False) == _ANALYZE.get_signal('foo', int(default_conf['ticker_interval']))
|
||||
assert (False, False) == _ANALYZE.get_signal('foo', default_conf['ticker_interval'])
|
||||
assert log_has('Unable to analyze ticker for pair foo: xyz', caplog.record_tuples)
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ def test_get_signal_empty_dataframe(default_conf, mocker, caplog):
|
||||
return_value=DataFrame([])
|
||||
)
|
||||
)
|
||||
assert (False, False) == _ANALYZE.get_signal('xyz', int(default_conf['ticker_interval']))
|
||||
assert (False, False) == _ANALYZE.get_signal('xyz', default_conf['ticker_interval'])
|
||||
assert log_has('Empty dataframe for pair xyz', caplog.record_tuples)
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog):
|
||||
return_value=DataFrame(ticks)
|
||||
)
|
||||
)
|
||||
assert (False, False) == _ANALYZE.get_signal('xyz', int(default_conf['ticker_interval']))
|
||||
assert (False, False) == _ANALYZE.get_signal('xyz', default_conf['ticker_interval'])
|
||||
assert log_has(
|
||||
'Outdated history for pair xyz. Last tick is 11 minutes old',
|
||||
caplog.record_tuples
|
||||
@@ -166,20 +166,16 @@ def test_get_signal_handles_exceptions(mocker):
|
||||
)
|
||||
)
|
||||
|
||||
assert _ANALYZE.get_signal('BTC-ETH', 5) == (False, False)
|
||||
assert _ANALYZE.get_signal('ETH/BTC', '5m') == (False, False)
|
||||
|
||||
|
||||
def test_parse_ticker_dataframe(ticker_history, ticker_history_without_bv):
|
||||
columns = ['date', 'close', 'high', 'low', 'open', 'volume']
|
||||
def test_parse_ticker_dataframe(ticker_history):
|
||||
columns = ['date', 'open', 'high', 'low', 'close', 'volume']
|
||||
|
||||
# Test file with BV data
|
||||
dataframe = Analyze.parse_ticker_dataframe(ticker_history)
|
||||
assert dataframe.columns.tolist() == columns
|
||||
|
||||
# Test file without BV data
|
||||
dataframe = Analyze.parse_ticker_dataframe(ticker_history_without_bv)
|
||||
assert dataframe.columns.tolist() == columns
|
||||
|
||||
|
||||
def test_tickerdata_to_dataframe(default_conf) -> None:
|
||||
"""
|
||||
@@ -188,7 +184,7 @@ def test_tickerdata_to_dataframe(default_conf) -> None:
|
||||
analyze = Analyze(default_conf)
|
||||
|
||||
timerange = ((None, 'line'), None, -100)
|
||||
tick = load_tickerdata_file(None, 'BTC_UNITEST', 1, timerange=timerange)
|
||||
tickerlist = {'BTC_UNITEST': tick}
|
||||
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange)
|
||||
tickerlist = {'UNITTEST/BTC': tick}
|
||||
data = analyze.tickerdata_to_dataframe(tickerlist)
|
||||
assert len(data['BTC_UNITEST']) == 100
|
||||
assert len(data['UNITTEST/BTC']) == 100
|
||||
|
@@ -55,10 +55,10 @@ def test_parse_args_verbose() -> None:
|
||||
|
||||
|
||||
def test_scripts_options() -> None:
|
||||
arguments = Arguments(['-p', 'BTC_ETH'], '')
|
||||
arguments = Arguments(['-p', 'ETH/BTC'], '')
|
||||
arguments.scripts_options()
|
||||
args = arguments.get_parsed_arg()
|
||||
assert args.pair == 'BTC_ETH'
|
||||
assert args.pair == 'ETH/BTC'
|
||||
|
||||
|
||||
def test_parse_args_version() -> None:
|
||||
@@ -109,6 +109,13 @@ def test_parse_args_dynamic_whitelist_invalid_values() -> None:
|
||||
def test_parse_timerange_incorrect() -> None:
|
||||
assert ((None, 'line'), None, -200) == Arguments.parse_timerange('-200')
|
||||
assert (('line', None), 200, None) == Arguments.parse_timerange('200-')
|
||||
assert (('index', 'index'), 200, 500) == Arguments.parse_timerange('200-500')
|
||||
|
||||
assert (('date', None), 1274486400, None) == Arguments.parse_timerange('20100522-')
|
||||
assert ((None, 'date'), None, 1274486400) == Arguments.parse_timerange('-20100522')
|
||||
timerange = Arguments.parse_timerange('20100522-20150730')
|
||||
assert timerange == (('date', 'date'), 1274486400, 1438214400)
|
||||
|
||||
with pytest.raises(Exception, match=r'Incorrect syntax.*'):
|
||||
Arguments.parse_timerange('-')
|
||||
|
||||
@@ -126,7 +133,7 @@ def test_parse_args_backtesting_custom() -> None:
|
||||
'-c', 'test_conf.json',
|
||||
'backtesting',
|
||||
'--live',
|
||||
'--ticker-interval', '1',
|
||||
'--ticker-interval', '1m',
|
||||
'--refresh-pairs-cached']
|
||||
call_args = Arguments(args, '').get_parsed_arg()
|
||||
assert call_args.config == 'test_conf.json'
|
||||
@@ -134,7 +141,7 @@ def test_parse_args_backtesting_custom() -> None:
|
||||
assert call_args.loglevel == logging.INFO
|
||||
assert call_args.subparser == 'backtesting'
|
||||
assert call_args.func is not None
|
||||
assert call_args.ticker_interval == 1
|
||||
assert call_args.ticker_interval == '1m'
|
||||
assert call_args.refresh_pairs is True
|
||||
|
||||
|
||||
|
@@ -13,6 +13,7 @@ from jsonschema import ValidationError
|
||||
from freqtrade.arguments import Arguments
|
||||
from freqtrade.configuration import Configuration
|
||||
from freqtrade.tests.conftest import log_has
|
||||
from freqtrade import OperationalException
|
||||
|
||||
|
||||
def test_configuration_object() -> None:
|
||||
@@ -28,19 +29,19 @@ def test_configuration_object() -> None:
|
||||
assert hasattr(Configuration, 'get_config')
|
||||
|
||||
|
||||
def test_load_config_invalid_pair(default_conf, mocker) -> None:
|
||||
def test_load_config_invalid_pair(default_conf) -> None:
|
||||
"""
|
||||
Test the configuration validator with an invalid PAIR format
|
||||
"""
|
||||
conf = deepcopy(default_conf)
|
||||
conf['exchange']['pair_whitelist'].append('BTC-ETH')
|
||||
conf['exchange']['pair_whitelist'].append('ETH-BTC')
|
||||
|
||||
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:
|
||||
def test_load_config_missing_attributes(default_conf) -> None:
|
||||
"""
|
||||
Test the configuration validator with a missing attribute
|
||||
"""
|
||||
@@ -68,6 +69,21 @@ def test_load_config_file(default_conf, mocker, caplog) -> None:
|
||||
assert log_has('Validating configuration ...', caplog.record_tuples)
|
||||
|
||||
|
||||
def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None:
|
||||
"""
|
||||
Test Configuration._load_config_file() method
|
||||
"""
|
||||
conf = deepcopy(default_conf)
|
||||
conf['max_open_trades'] = 0
|
||||
file_mock = mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||
read_data=json.dumps(conf)
|
||||
))
|
||||
|
||||
Configuration([])._load_config_file('somefile')
|
||||
assert file_mock.call_count == 1
|
||||
assert log_has('Validating configuration ...', caplog.record_tuples)
|
||||
|
||||
|
||||
def test_load_config_file_exception(mocker, caplog) -> None:
|
||||
"""
|
||||
Test Configuration._load_config_file() method
|
||||
@@ -251,7 +267,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'--datadir', '/foo/bar',
|
||||
'backtesting',
|
||||
'--ticker-interval', '1',
|
||||
'--ticker-interval', '1m',
|
||||
'--live',
|
||||
'--realistic-simulation',
|
||||
'--refresh-pairs-cached',
|
||||
@@ -276,7 +292,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
||||
assert 'ticker_interval' in config
|
||||
assert log_has('Parameter -i/--ticker-interval detected ...', caplog.record_tuples)
|
||||
assert log_has(
|
||||
'Using ticker_interval: 1 ...',
|
||||
'Using ticker_interval: 1m ...',
|
||||
caplog.record_tuples
|
||||
)
|
||||
|
||||
@@ -334,3 +350,29 @@ def test_hyperopt_with_arguments(mocker, default_conf, caplog) -> None:
|
||||
assert 'spaces' in config
|
||||
assert config['spaces'] == ['all']
|
||||
assert log_has('Parameter -s/--spaces detected: [\'all\']', caplog.record_tuples)
|
||||
|
||||
|
||||
def test_check_exchange(default_conf) -> None:
|
||||
"""
|
||||
Test the configuration validator with a missing attribute
|
||||
"""
|
||||
conf = deepcopy(default_conf)
|
||||
configuration = Configuration([])
|
||||
|
||||
# Test a valid exchange
|
||||
conf.get('exchange').update({'name': 'BITTREX'})
|
||||
assert configuration.check_exchange(conf)
|
||||
|
||||
# Test a valid exchange
|
||||
conf.get('exchange').update({'name': 'binance'})
|
||||
assert configuration.check_exchange(conf)
|
||||
|
||||
# Test a invalid exchange
|
||||
conf.get('exchange').update({'name': 'unknown_exchange'})
|
||||
configuration.config = conf
|
||||
|
||||
with pytest.raises(
|
||||
OperationalException,
|
||||
match=r'.*Exchange "unknown_exchange" not supported.*'
|
||||
):
|
||||
configuration.check_exchange(conf)
|
||||
|
@@ -6,11 +6,11 @@ from freqtrade.analyze import Analyze
|
||||
from freqtrade.optimize import load_data
|
||||
from freqtrade.strategy.resolver import StrategyResolver
|
||||
|
||||
_pairs = ['BTC_ETH']
|
||||
_pairs = ['ETH/BTC']
|
||||
|
||||
|
||||
def load_dataframe_pair(pairs):
|
||||
ld = load_data(None, ticker_interval=5, pairs=pairs)
|
||||
ld = load_data(None, ticker_interval='5m', pairs=pairs)
|
||||
assert isinstance(ld, dict)
|
||||
assert isinstance(pairs[0], str)
|
||||
dataframe = ld[pairs[0]]
|
||||
|
@@ -6,7 +6,10 @@ from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
from freqtrade.fiat_convert import CryptoFiat, CryptoToFiatConverter
|
||||
from freqtrade.tests.conftest import patch_coinmarketcap
|
||||
|
||||
|
||||
def test_pair_convertion_object():
|
||||
@@ -77,8 +80,7 @@ def test_fiat_convert_find_price(mocker):
|
||||
with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'):
|
||||
fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='ABC')
|
||||
|
||||
with pytest.raises(ValueError, match=r'The crypto symbol XRP is not supported.'):
|
||||
fiat_convert.get_price(crypto_symbol='XRP', fiat_symbol='USD')
|
||||
assert fiat_convert.get_price(crypto_symbol='XRP', fiat_symbol='USD') == 0.0
|
||||
|
||||
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=12345.0)
|
||||
assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 12345.0
|
||||
@@ -124,12 +126,38 @@ def test_fiat_convert_get_price(mocker):
|
||||
assert fiat_convert._pairs[0]._expiration is not expiration
|
||||
|
||||
|
||||
def test_loadcryptomap(mocker):
|
||||
patch_coinmarketcap(mocker)
|
||||
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
assert len(fiat_convert._cryptomap) == 2
|
||||
|
||||
assert fiat_convert._cryptomap["BTC"] == "1"
|
||||
|
||||
|
||||
def test_fiat_init_network_exception(mocker):
|
||||
# Because CryptoToFiatConverter is a Singleton we reset the listings
|
||||
listmock = MagicMock(side_effect=RequestException)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.fiat_convert.Market',
|
||||
listings=listmock,
|
||||
)
|
||||
# with pytest.raises(RequestEsxception):
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
fiat_convert._cryptomap = {}
|
||||
fiat_convert._load_cryptomap()
|
||||
|
||||
assert len(fiat_convert._cryptomap) == 0
|
||||
|
||||
|
||||
def test_fiat_convert_without_network():
|
||||
# Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap
|
||||
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
|
||||
cmc_temp = CryptoToFiatConverter._coinmarketcap
|
||||
CryptoToFiatConverter._coinmarketcap = None
|
||||
|
||||
assert fiat_convert._coinmarketcap is None
|
||||
assert fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='USD') == 0.0
|
||||
CryptoToFiatConverter._coinmarketcap = cmc_temp
|
||||
|
@@ -8,7 +8,6 @@ import logging
|
||||
import re
|
||||
import time
|
||||
from copy import deepcopy
|
||||
from typing import Dict, Optional
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import arrow
|
||||
@@ -16,12 +15,11 @@ import pytest
|
||||
import requests
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
from freqtrade import DependencyException, OperationalException
|
||||
from freqtrade.exchange import Exchanges
|
||||
from freqtrade import DependencyException, OperationalException, TemporaryError
|
||||
from freqtrade.freqtradebot import FreqtradeBot
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.state import State
|
||||
from freqtrade.tests.conftest import log_has
|
||||
from freqtrade.tests.conftest import log_has, patch_coinmarketcap
|
||||
|
||||
|
||||
# Functions for recurrent object patching
|
||||
@@ -65,20 +63,6 @@ def patch_RPCManager(mocker) -> MagicMock:
|
||||
return rpc_mock
|
||||
|
||||
|
||||
def patch_coinmarketcap(mocker, value: Optional[Dict[str, float]] = None) -> None:
|
||||
"""
|
||||
Mocker to coinmarketcap to speed up tests
|
||||
:param mocker: mocker to patch coinmarketcap class
|
||||
:return: None
|
||||
"""
|
||||
mock = MagicMock()
|
||||
|
||||
if value:
|
||||
mock.ticker = {'price_usd': 12345.0}
|
||||
|
||||
mocker.patch('freqtrade.fiat_convert.Market', mock)
|
||||
|
||||
|
||||
# Unit tests
|
||||
def test_freqtradebot_object() -> None:
|
||||
"""
|
||||
@@ -202,29 +186,28 @@ def test_throttle_with_assets(mocker, default_conf) -> None:
|
||||
assert result == -1
|
||||
|
||||
|
||||
def test_gen_pair_whitelist(mocker, default_conf, get_market_summaries_data) -> None:
|
||||
def test_gen_pair_whitelist(mocker, default_conf, tickers) -> None:
|
||||
"""
|
||||
Test _gen_pair_whitelist() method
|
||||
"""
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
mocker.patch(
|
||||
'freqtrade.freqtradebot.exchange.get_market_summaries',
|
||||
return_value=get_market_summaries_data
|
||||
)
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.get_tickers', tickers)
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.exchange_has', MagicMock(return_value=True))
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
|
||||
# Test to retrieved BTC sorted on BaseVolume
|
||||
# Test to retrieved BTC sorted on quoteVolume (default)
|
||||
whitelist = freqtrade._gen_pair_whitelist(base_currency='BTC')
|
||||
assert whitelist == ['BTC_ZCL', 'BTC_ZEC', 'BTC_XZC', 'BTC_XWC']
|
||||
assert whitelist == ['ETH/BTC', 'TKN/BTC', 'BLK/BTC', 'LTC/BTC']
|
||||
|
||||
# Test to retrieved BTC sorted on OpenBuyOrders
|
||||
whitelist = freqtrade._gen_pair_whitelist(base_currency='BTC', key='OpenBuyOrders')
|
||||
assert whitelist == ['BTC_XWC', 'BTC_ZCL', 'BTC_ZEC', 'BTC_XZC']
|
||||
# Test to retrieve BTC sorted on bidVolume
|
||||
whitelist = freqtrade._gen_pair_whitelist(base_currency='BTC', key='bidVolume')
|
||||
assert whitelist == ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'BLK/BTC']
|
||||
|
||||
# Test with USDT sorted on BaseVolume
|
||||
# Test with USDT sorted on quoteVolume (default)
|
||||
whitelist = freqtrade._gen_pair_whitelist(base_currency='USDT')
|
||||
assert whitelist == ['USDT_XRP', 'USDT_XVG', 'USDT_XMR', 'USDT_ZEC']
|
||||
assert whitelist == ['TKN/USDT', 'ETH/USDT', 'LTC/USDT', 'BLK/USDT']
|
||||
|
||||
# Test with ETH (our fixture does not have ETH, but Bittrex returns them)
|
||||
# Test with ETH (our fixture does not have ETH, so result should be empty)
|
||||
whitelist = freqtrade._gen_pair_whitelist(base_currency='ETH')
|
||||
assert whitelist == []
|
||||
|
||||
@@ -237,7 +220,7 @@ def test_refresh_whitelist() -> None:
|
||||
pass
|
||||
|
||||
|
||||
def test_create_trade(default_conf, ticker, limit_buy_order, mocker) -> None:
|
||||
def test_create_trade(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test create_trade() method
|
||||
"""
|
||||
@@ -248,7 +231,8 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
# Save state of current whitelist
|
||||
@@ -261,7 +245,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker) -> None:
|
||||
assert trade.stake_amount == 0.001
|
||||
assert trade.is_open
|
||||
assert trade.open_date is not None
|
||||
assert trade.exchange == Exchanges.BITTREX.name
|
||||
assert trade.exchange == 'bittrex'
|
||||
|
||||
# Simulate fulfilled LIMIT_BUY order for trade
|
||||
trade.update(limit_buy_order)
|
||||
@@ -272,19 +256,20 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker) -> None:
|
||||
assert whitelist == default_conf['exchange']['pair_whitelist']
|
||||
|
||||
|
||||
def test_create_trade_minimal_amount(default_conf, ticker, mocker) -> None:
|
||||
def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test create_trade() method
|
||||
"""
|
||||
patch_get_signal(mocker)
|
||||
patch_RPCManager(mocker)
|
||||
patch_coinmarketcap(mocker)
|
||||
buy_mock = MagicMock(return_value='mocked_limit_buy')
|
||||
buy_mock = MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=buy_mock
|
||||
buy=buy_mock,
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
@@ -296,7 +281,7 @@ def test_create_trade_minimal_amount(default_conf, ticker, mocker) -> None:
|
||||
assert rate * amount >= conf['stake_amount']
|
||||
|
||||
|
||||
def test_create_trade_no_stake_amount(default_conf, ticker, mocker) -> None:
|
||||
def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test create_trade() method
|
||||
"""
|
||||
@@ -307,8 +292,9 @@ def test_create_trade_no_stake_amount(default_conf, ticker, mocker) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_limit_buy'),
|
||||
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5)
|
||||
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://'))
|
||||
|
||||
@@ -316,7 +302,7 @@ def test_create_trade_no_stake_amount(default_conf, ticker, mocker) -> None:
|
||||
freqtrade.create_trade()
|
||||
|
||||
|
||||
def test_create_trade_no_pairs(default_conf, ticker, mocker) -> None:
|
||||
def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test create_trade() method
|
||||
"""
|
||||
@@ -327,12 +313,13 @@ def test_create_trade_no_pairs(default_conf, ticker, mocker) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
conf['exchange']['pair_whitelist'] = ["BTC_ETH"]
|
||||
conf['exchange']['pair_blacklist'] = ["BTC_ETH"]
|
||||
conf['exchange']['pair_whitelist'] = ["ETH/BTC"]
|
||||
conf['exchange']['pair_blacklist'] = ["ETH/BTC"]
|
||||
freqtrade = FreqtradeBot(conf, create_engine('sqlite://'))
|
||||
|
||||
freqtrade.create_trade()
|
||||
@@ -341,7 +328,8 @@ def test_create_trade_no_pairs(default_conf, ticker, mocker) -> None:
|
||||
freqtrade.create_trade()
|
||||
|
||||
|
||||
def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker) -> None:
|
||||
def test_create_trade_no_pairs_after_blacklist(default_conf, ticker,
|
||||
limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test create_trade() method
|
||||
"""
|
||||
@@ -352,12 +340,13 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker) ->
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
conf['exchange']['pair_whitelist'] = ["BTC_ETH"]
|
||||
conf['exchange']['pair_blacklist'] = ["BTC_ETH"]
|
||||
conf['exchange']['pair_whitelist'] = ["ETH/BTC"]
|
||||
conf['exchange']['pair_blacklist'] = ["ETH/BTC"]
|
||||
freqtrade = FreqtradeBot(conf, create_engine('sqlite://'))
|
||||
|
||||
freqtrade.create_trade()
|
||||
@@ -366,7 +355,7 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker) ->
|
||||
freqtrade.create_trade()
|
||||
|
||||
|
||||
def test_create_trade_no_signal(default_conf, mocker) -> None:
|
||||
def test_create_trade_no_signal(default_conf, fee, mocker) -> None:
|
||||
"""
|
||||
Test create_trade() method
|
||||
"""
|
||||
@@ -380,7 +369,8 @@ def test_create_trade_no_signal(default_conf, mocker) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker_history=MagicMock(return_value=20),
|
||||
get_balance=MagicMock(return_value=20)
|
||||
get_balance=MagicMock(return_value=20),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
@@ -393,7 +383,7 @@ def test_create_trade_no_signal(default_conf, mocker) -> None:
|
||||
|
||||
|
||||
def test_process_trade_creation(default_conf, ticker, limit_buy_order,
|
||||
health, mocker, caplog) -> None:
|
||||
markets, fee, mocker, caplog) -> None:
|
||||
"""
|
||||
Test the trade creation in _process() method
|
||||
"""
|
||||
@@ -404,9 +394,10 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order,
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
get_wallet_health=health,
|
||||
buy=MagicMock(return_value='mocked_limit_buy'),
|
||||
get_order=MagicMock(return_value=limit_buy_order)
|
||||
get_markets=markets,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_order=MagicMock(return_value=limit_buy_order),
|
||||
get_fee=fee,
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
||||
@@ -423,7 +414,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order,
|
||||
assert trade.stake_amount == default_conf['stake_amount']
|
||||
assert trade.is_open
|
||||
assert trade.open_date is not None
|
||||
assert trade.exchange == Exchanges.BITTREX.name
|
||||
assert trade.exchange == 'bittrex'
|
||||
assert trade.open_rate == 0.00001099
|
||||
assert trade.amount == 90.99181073703367
|
||||
|
||||
@@ -433,7 +424,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order,
|
||||
)
|
||||
|
||||
|
||||
def test_process_exchange_failures(default_conf, ticker, health, mocker) -> None:
|
||||
def test_process_exchange_failures(default_conf, ticker, markets, mocker) -> None:
|
||||
"""
|
||||
Test _process() method when a RequestException happens
|
||||
"""
|
||||
@@ -444,8 +435,8 @@ def test_process_exchange_failures(default_conf, ticker, health, mocker) -> None
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
get_wallet_health=health,
|
||||
buy=MagicMock(side_effect=requests.exceptions.RequestException)
|
||||
get_markets=markets,
|
||||
buy=MagicMock(side_effect=TemporaryError)
|
||||
)
|
||||
sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None)
|
||||
|
||||
@@ -455,7 +446,7 @@ def test_process_exchange_failures(default_conf, ticker, health, mocker) -> None
|
||||
assert sleep_mock.has_calls()
|
||||
|
||||
|
||||
def test_process_operational_exception(default_conf, ticker, health, mocker) -> None:
|
||||
def test_process_operational_exception(default_conf, ticker, markets, mocker) -> None:
|
||||
"""
|
||||
Test _process() method when an OperationalException happens
|
||||
"""
|
||||
@@ -466,7 +457,7 @@ def test_process_operational_exception(default_conf, ticker, health, mocker) ->
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
get_wallet_health=health,
|
||||
get_markets=markets,
|
||||
buy=MagicMock(side_effect=OperationalException)
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@@ -478,7 +469,8 @@ def test_process_operational_exception(default_conf, ticker, health, mocker) ->
|
||||
assert 'OperationalException' in msg_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
||||
def test_process_trade_handling(default_conf, ticker, limit_buy_order, health, mocker) -> None:
|
||||
def test_process_trade_handling(
|
||||
default_conf, ticker, limit_buy_order, markets, fee, mocker) -> None:
|
||||
"""
|
||||
Test _process()
|
||||
"""
|
||||
@@ -489,9 +481,10 @@ def test_process_trade_handling(default_conf, ticker, limit_buy_order, health, m
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
get_wallet_health=health,
|
||||
buy=MagicMock(return_value='mocked_limit_buy'),
|
||||
get_order=MagicMock(return_value=limit_buy_order)
|
||||
get_markets=markets,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_order=MagicMock(return_value=limit_buy_order),
|
||||
get_fee=fee,
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
||||
@@ -560,25 +553,37 @@ def test_process_maybe_execute_buy_exception(mocker, default_conf, caplog) -> No
|
||||
log_has('Unable to create trade:', caplog.record_tuples)
|
||||
|
||||
|
||||
def test_process_maybe_execute_sell(mocker, default_conf) -> None:
|
||||
def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplog) -> None:
|
||||
"""
|
||||
Test process_maybe_execute_sell() method
|
||||
"""
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.get_order', return_value=1)
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.get_order', return_value=limit_buy_order)
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.get_trades_for_order', return_value=[])
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
|
||||
return_value=limit_buy_order['amount'])
|
||||
|
||||
trade = MagicMock()
|
||||
trade.open_order_id = '123'
|
||||
trade.open_fee = 0.001
|
||||
assert not freqtrade.process_maybe_execute_sell(trade)
|
||||
# Test amount not modified by fee-logic
|
||||
assert not log_has('Applying fee to amount for Trade {} from 90.99181073 to 90.81'.format(
|
||||
trade), caplog.record_tuples)
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81)
|
||||
# test amount modified by fee-logic
|
||||
assert not freqtrade.process_maybe_execute_sell(trade)
|
||||
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
# Assert we call handle_trade() if trade is feasible for execution
|
||||
assert freqtrade.process_maybe_execute_sell(trade)
|
||||
|
||||
|
||||
def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker) -> None:
|
||||
def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test check_handle() method
|
||||
"""
|
||||
@@ -592,8 +597,9 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker) -
|
||||
'ask': 0.00001173,
|
||||
'last': 0.00001172
|
||||
}),
|
||||
buy=MagicMock(return_value='mocked_limit_buy'),
|
||||
sell=MagicMock(return_value='mocked_limit_sell')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
||||
get_fee=fee
|
||||
)
|
||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
||||
|
||||
@@ -604,12 +610,13 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker) -
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
|
||||
time.sleep(0.01) # Race condition fix
|
||||
trade.update(limit_buy_order)
|
||||
assert trade.is_open is True
|
||||
|
||||
patch_get_signal(mocker, value=(False, True))
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.open_order_id == 'mocked_limit_sell'
|
||||
assert trade.open_order_id == limit_sell_order['id']
|
||||
|
||||
# Simulate fulfilled LIMIT_SELL order for trade
|
||||
trade.update(limit_sell_order)
|
||||
@@ -620,7 +627,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker) -
|
||||
assert trade.close_date is not None
|
||||
|
||||
|
||||
def test_handle_overlpapping_signals(default_conf, ticker, mocker) -> None:
|
||||
def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test check_handle() method
|
||||
"""
|
||||
@@ -635,7 +642,8 @@ def test_handle_overlpapping_signals(default_conf, ticker, mocker) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
freqtrade = FreqtradeBot(conf, create_engine('sqlite://'))
|
||||
@@ -677,7 +685,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, mocker) -> None:
|
||||
assert freqtrade.handle_trade(trades[0]) is True
|
||||
|
||||
|
||||
def test_handle_trade_roi(default_conf, ticker, mocker, caplog) -> None:
|
||||
def test_handle_trade_roi(default_conf, ticker, limit_buy_order, fee, mocker, caplog) -> None:
|
||||
"""
|
||||
Test check_handle() method
|
||||
"""
|
||||
@@ -692,7 +700,8 @@ def test_handle_trade_roi(default_conf, ticker, mocker, caplog) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=True)
|
||||
@@ -712,7 +721,8 @@ def test_handle_trade_roi(default_conf, ticker, mocker, caplog) -> None:
|
||||
assert log_has('Required profit reached. Selling..', caplog.record_tuples)
|
||||
|
||||
|
||||
def test_handle_trade_experimental(default_conf, ticker, mocker, caplog) -> None:
|
||||
def test_handle_trade_experimental(
|
||||
default_conf, ticker, limit_buy_order, fee, mocker, caplog) -> None:
|
||||
"""
|
||||
Test check_handle() method
|
||||
"""
|
||||
@@ -727,7 +737,8 @@ def test_handle_trade_experimental(default_conf, ticker, mocker, caplog) -> None
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False)
|
||||
|
||||
@@ -745,7 +756,7 @@ def test_handle_trade_experimental(default_conf, ticker, mocker, caplog) -> None
|
||||
assert log_has('Sell signal received. Selling..', caplog.record_tuples)
|
||||
|
||||
|
||||
def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mocker) -> None:
|
||||
def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test check_handle() method
|
||||
"""
|
||||
@@ -756,7 +767,8 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mo
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
||||
@@ -774,7 +786,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mo
|
||||
freqtrade.handle_trade(trade)
|
||||
|
||||
|
||||
def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, mocker) -> None:
|
||||
def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, fee, mocker) -> None:
|
||||
"""
|
||||
Test check_handle_timedout() method
|
||||
"""
|
||||
@@ -786,17 +798,19 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, mo
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
get_order=MagicMock(return_value=limit_buy_order_old),
|
||||
cancel_order=cancel_order_mock
|
||||
cancel_order=cancel_order_mock,
|
||||
get_fee=fee
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
||||
trade_buy = Trade(
|
||||
pair='BTC_ETH',
|
||||
pair='ETH/BTC',
|
||||
open_rate=0.00001099,
|
||||
exchange='BITTREX',
|
||||
exchange='bittrex',
|
||||
open_order_id='123456789',
|
||||
amount=90.99181073,
|
||||
fee=0.0,
|
||||
fee_open=0.0,
|
||||
fee_close=0.0,
|
||||
stake_amount=1,
|
||||
open_date=arrow.utcnow().shift(minutes=-601).datetime,
|
||||
is_open=True
|
||||
@@ -830,12 +844,13 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old,
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
||||
trade_sell = Trade(
|
||||
pair='BTC_ETH',
|
||||
pair='ETH/BTC',
|
||||
open_rate=0.00001099,
|
||||
exchange='BITTREX',
|
||||
exchange='bittrex',
|
||||
open_order_id='123456789',
|
||||
amount=90.99181073,
|
||||
fee=0.0,
|
||||
fee_open=0.0,
|
||||
fee_close=0.0,
|
||||
stake_amount=1,
|
||||
open_date=arrow.utcnow().shift(hours=-5).datetime,
|
||||
close_date=arrow.utcnow().shift(minutes=-601).datetime,
|
||||
@@ -869,12 +884,13 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
||||
trade_buy = Trade(
|
||||
pair='BTC_ETH',
|
||||
pair='ETH/BTC',
|
||||
open_rate=0.00001099,
|
||||
exchange='BITTREX',
|
||||
exchange='bittrex',
|
||||
open_order_id='123456789',
|
||||
amount=90.99181073,
|
||||
fee=0.0,
|
||||
fee_open=0.0,
|
||||
fee_close=0.0,
|
||||
stake_amount=1,
|
||||
open_date=arrow.utcnow().shift(minutes=-601).datetime,
|
||||
is_open=True
|
||||
@@ -916,12 +932,13 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) -
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
||||
trade_buy = Trade(
|
||||
pair='BTC_ETH',
|
||||
pair='ETH/BTC',
|
||||
open_rate=0.00001099,
|
||||
exchange='BITTREX',
|
||||
exchange='bittrex',
|
||||
open_order_id='123456789',
|
||||
amount=90.99181073,
|
||||
fee=0.0,
|
||||
fee_open=0.0,
|
||||
fee_close=0.0,
|
||||
stake_amount=1,
|
||||
open_date=arrow.utcnow().shift(minutes=-601).datetime,
|
||||
is_open=True
|
||||
@@ -929,7 +946,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) -
|
||||
|
||||
Trade.session.add(trade_buy)
|
||||
regexp = re.compile(
|
||||
'Cannot query order for Trade(id=1, pair=BTC_ETH, amount=90.99181073, '
|
||||
'Cannot query order for Trade(id=1, pair=ETH/BTC, amount=90.99181073, '
|
||||
'open_rate=0.00001099, open_since=10 hours ago) due to Traceback (most '
|
||||
'recent call last):\n.*'
|
||||
)
|
||||
@@ -990,7 +1007,7 @@ def test_handle_timedout_limit_sell(mocker, default_conf) -> None:
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
|
||||
def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker) -> None:
|
||||
def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None:
|
||||
"""
|
||||
Test execute_sell() method with a ticker going UP
|
||||
"""
|
||||
@@ -1000,7 +1017,8 @@ def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker) -> None:
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@@ -1022,7 +1040,7 @@ def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker) -> None:
|
||||
|
||||
assert rpc_mock.call_count == 2
|
||||
assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert 'Profit' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0]
|
||||
@@ -1030,7 +1048,7 @@ def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker) -> None:
|
||||
assert '0.919 USD' in rpc_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
||||
def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker) -> None:
|
||||
def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) -> None:
|
||||
"""
|
||||
Test execute_sell() method with a ticker going DOWN
|
||||
"""
|
||||
@@ -1041,7 +1059,8 @@ def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker) -> No
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
||||
@@ -1062,14 +1081,15 @@ def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker) -> No
|
||||
|
||||
assert rpc_mock.call_count == 2
|
||||
assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '0.00001044' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '-0.824 USD' in rpc_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
||||
def test_execute_sell_without_conf_sell_up(default_conf, ticker, ticker_sell_up, mocker) -> None:
|
||||
def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee,
|
||||
ticker_sell_up, mocker) -> None:
|
||||
"""
|
||||
Test execute_sell() method with a ticker going DOWN and with a bot config empty
|
||||
"""
|
||||
@@ -1079,7 +1099,8 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, ticker_sell_up,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
||||
@@ -1101,14 +1122,14 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, ticker_sell_up,
|
||||
|
||||
assert rpc_mock.call_count == 2
|
||||
assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '(profit: 6.11%, 0.00006126)' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert 'USD' not in rpc_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
||||
def test_execute_sell_without_conf_sell_down(default_conf, ticker,
|
||||
def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee,
|
||||
ticker_sell_down, mocker) -> None:
|
||||
"""
|
||||
Test execute_sell() method with a ticker going DOWN and with a bot config empty
|
||||
@@ -1119,7 +1140,8 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
||||
@@ -1141,12 +1163,12 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker,
|
||||
|
||||
assert rpc_mock.call_count == 2
|
||||
assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert '0.00001044' in rpc_mock.call_args_list[-1][0][0]
|
||||
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
||||
def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker) -> None:
|
||||
def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test sell_profit_only feature when enabled
|
||||
"""
|
||||
@@ -1162,7 +1184,8 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker) -
|
||||
'ask': 0.00002173,
|
||||
'last': 0.00002172
|
||||
}),
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
conf = deepcopy(default_conf)
|
||||
conf['experimental'] = {
|
||||
@@ -1178,7 +1201,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker) -
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
|
||||
|
||||
def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker) -> None:
|
||||
def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test sell_profit_only feature when disabled
|
||||
"""
|
||||
@@ -1194,7 +1217,8 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker)
|
||||
'ask': 0.00002173,
|
||||
'last': 0.00002172
|
||||
}),
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
conf = deepcopy(default_conf)
|
||||
conf['experimental'] = {
|
||||
@@ -1210,7 +1234,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker)
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
|
||||
|
||||
def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker) -> None:
|
||||
def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test sell_profit_only feature when enabled and we have a loss
|
||||
"""
|
||||
@@ -1226,7 +1250,8 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker) ->
|
||||
'ask': 0.00000173,
|
||||
'last': 0.00000172
|
||||
}),
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
conf = deepcopy(default_conf)
|
||||
conf['experimental'] = {
|
||||
@@ -1242,7 +1267,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker) ->
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
|
||||
|
||||
def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker) -> None:
|
||||
def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test sell_profit_only feature when enabled and we have a loss
|
||||
"""
|
||||
@@ -1258,7 +1283,8 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker) ->
|
||||
'ask': 0.00000173,
|
||||
'last': 0.00000172
|
||||
}),
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
@@ -1274,3 +1300,211 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker) ->
|
||||
trade.update(limit_buy_order)
|
||||
patch_get_signal(mocker, value=(False, True))
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
|
||||
|
||||
def test_get_real_amount_quote(default_conf, trades_for_order, buy_order_fee, caplog, mocker):
|
||||
"""
|
||||
Test get_real_amount - fee in quote currency
|
||||
"""
|
||||
|
||||
mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=trades_for_order)
|
||||
|
||||
patch_get_signal(mocker)
|
||||
patch_RPCManager(mocker)
|
||||
patch_coinmarketcap(mocker)
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
amount = sum(x['amount'] for x in trades_for_order)
|
||||
trade = Trade(
|
||||
pair='LTC/ETH',
|
||||
amount=amount,
|
||||
exchange='binance',
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
# Amount is reduced by "fee"
|
||||
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount - (amount * 0.001)
|
||||
assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
|
||||
'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992) from Trades',
|
||||
caplog.record_tuples)
|
||||
|
||||
|
||||
def test_get_real_amount_no_trade(default_conf, buy_order_fee, caplog, mocker):
|
||||
"""
|
||||
Test get_real_amount - fee in quote currency
|
||||
"""
|
||||
|
||||
mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=[])
|
||||
|
||||
patch_get_signal(mocker)
|
||||
patch_RPCManager(mocker)
|
||||
patch_coinmarketcap(mocker)
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
amount = buy_order_fee['amount']
|
||||
trade = Trade(
|
||||
pair='LTC/ETH',
|
||||
amount=amount,
|
||||
exchange='binance',
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
# Amount is reduced by "fee"
|
||||
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount
|
||||
assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
|
||||
'open_rate=0.24544100, open_since=closed) failed: myTrade-Dict empty found',
|
||||
caplog.record_tuples)
|
||||
|
||||
|
||||
def test_get_real_amount_stake(default_conf, trades_for_order, buy_order_fee, caplog, mocker):
|
||||
"""
|
||||
Test get_real_amount - fees in Stake currency
|
||||
"""
|
||||
trades_for_order[0]['fee']['currency'] = 'ETH'
|
||||
|
||||
patch_get_signal(mocker)
|
||||
patch_RPCManager(mocker)
|
||||
patch_coinmarketcap(mocker)
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=trades_for_order)
|
||||
amount = sum(x['amount'] for x in trades_for_order)
|
||||
trade = Trade(
|
||||
pair='LTC/ETH',
|
||||
amount=amount,
|
||||
exchange='binance',
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
# Amount does not change
|
||||
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount
|
||||
|
||||
|
||||
def test_get_real_amount_BNB(default_conf, trades_for_order, buy_order_fee, mocker):
|
||||
"""
|
||||
Test get_real_amount - Fees in BNB
|
||||
"""
|
||||
|
||||
trades_for_order[0]['fee']['currency'] = 'BNB'
|
||||
trades_for_order[0]['fee']['cost'] = 0.00094518
|
||||
|
||||
patch_get_signal(mocker)
|
||||
patch_RPCManager(mocker)
|
||||
patch_coinmarketcap(mocker)
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=trades_for_order)
|
||||
amount = sum(x['amount'] for x in trades_for_order)
|
||||
trade = Trade(
|
||||
pair='LTC/ETH',
|
||||
amount=amount,
|
||||
exchange='binance',
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
# Amount does not change
|
||||
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount
|
||||
|
||||
|
||||
def test_get_real_amount_multi(default_conf, trades_for_order2, buy_order_fee, caplog, mocker):
|
||||
"""
|
||||
Test get_real_amount with split trades (multiple trades for this order)
|
||||
"""
|
||||
|
||||
patch_get_signal(mocker)
|
||||
patch_RPCManager(mocker)
|
||||
patch_coinmarketcap(mocker)
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=trades_for_order2)
|
||||
amount = float(sum(x['amount'] for x in trades_for_order2))
|
||||
trade = Trade(
|
||||
pair='LTC/ETH',
|
||||
amount=amount,
|
||||
exchange='binance',
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
# Amount is reduced by "fee"
|
||||
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount - (amount * 0.001)
|
||||
assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
|
||||
'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992) from Trades',
|
||||
caplog.record_tuples)
|
||||
|
||||
|
||||
def test_get_real_amount_fromorder(default_conf, trades_for_order, buy_order_fee, caplog, mocker):
|
||||
"""
|
||||
Test get_real_amount with split trades (multiple trades for this order)
|
||||
"""
|
||||
limit_buy_order = deepcopy(buy_order_fee)
|
||||
limit_buy_order['fee'] = {'cost': 0.004, 'currency': 'LTC'}
|
||||
|
||||
patch_get_signal(mocker)
|
||||
patch_RPCManager(mocker)
|
||||
patch_coinmarketcap(mocker)
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=[trades_for_order])
|
||||
amount = float(sum(x['amount'] for x in trades_for_order))
|
||||
trade = Trade(
|
||||
pair='LTC/ETH',
|
||||
amount=amount,
|
||||
exchange='binance',
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
# Amount is reduced by "fee"
|
||||
assert freqtrade.get_real_amount(trade, limit_buy_order) == amount - 0.004
|
||||
assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
|
||||
'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.996) from Order',
|
||||
caplog.record_tuples)
|
||||
|
||||
|
||||
def test_get_real_amount_invalid_order(default_conf, trades_for_order, buy_order_fee, mocker):
|
||||
"""
|
||||
Test get_real_amount with split trades (multiple trades for this order)
|
||||
"""
|
||||
limit_buy_order = deepcopy(buy_order_fee)
|
||||
limit_buy_order['fee'] = {'cost': 0.004}
|
||||
|
||||
patch_get_signal(mocker)
|
||||
patch_RPCManager(mocker)
|
||||
patch_coinmarketcap(mocker)
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=[])
|
||||
amount = float(sum(x['amount'] for x in trades_for_order))
|
||||
trade = Trade(
|
||||
pair='LTC/ETH',
|
||||
amount=amount,
|
||||
exchange='binance',
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
# Amount does not change
|
||||
assert freqtrade.get_real_amount(trade, limit_buy_order) == amount
|
||||
|
||||
|
||||
def test_get_real_amount_invalid(default_conf, trades_for_order, buy_order_fee, mocker):
|
||||
"""
|
||||
Test get_real_amount - fees in Stake currency
|
||||
"""
|
||||
# Remove "Currency" from fee dict
|
||||
trades_for_order[0]['fee'] = {'cost': 0.008}
|
||||
|
||||
patch_get_signal(mocker)
|
||||
patch_RPCManager(mocker)
|
||||
patch_coinmarketcap(mocker)
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=trades_for_order)
|
||||
amount = sum(x['amount'] for x in trades_for_order)
|
||||
trade = Trade(
|
||||
pair='LTC/ETH',
|
||||
amount=amount,
|
||||
exchange='binance',
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
# Amount does not change
|
||||
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount
|
||||
|
@@ -9,7 +9,7 @@ from unittest.mock import MagicMock
|
||||
|
||||
from freqtrade.analyze import Analyze
|
||||
from freqtrade.misc import (shorten_date, datesarray_to_datetimearray,
|
||||
common_datearray, file_dump_json)
|
||||
common_datearray, file_dump_json, format_ms_time)
|
||||
from freqtrade.optimize.__init__ import load_tickerdata_file
|
||||
|
||||
|
||||
@@ -42,21 +42,21 @@ def test_datesarray_to_datetimearray(ticker_history):
|
||||
assert date_len == 3
|
||||
|
||||
|
||||
def test_common_datearray(default_conf, mocker) -> None:
|
||||
def test_common_datearray(default_conf) -> None:
|
||||
"""
|
||||
Test common_datearray()
|
||||
:return: None
|
||||
"""
|
||||
analyze = Analyze(default_conf)
|
||||
tick = load_tickerdata_file(None, 'BTC_UNITEST', 1)
|
||||
tickerlist = {'BTC_UNITEST': tick}
|
||||
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
||||
tickerlist = {'UNITTEST/BTC': tick}
|
||||
dataframes = analyze.tickerdata_to_dataframe(tickerlist)
|
||||
|
||||
dates = common_datearray(dataframes)
|
||||
|
||||
assert dates.size == dataframes['BTC_UNITEST']['date'].size
|
||||
assert dates[0] == dataframes['BTC_UNITEST']['date'][0]
|
||||
assert dates[-1] == dataframes['BTC_UNITEST']['date'][-1]
|
||||
assert dates.size == dataframes['UNITTEST/BTC']['date'].size
|
||||
assert dates[0] == dataframes['UNITTEST/BTC']['date'][0]
|
||||
assert dates[-1] == dataframes['UNITTEST/BTC']['date'][-1]
|
||||
|
||||
|
||||
def test_file_dump_json(mocker) -> None:
|
||||
@@ -69,3 +69,25 @@ def test_file_dump_json(mocker) -> None:
|
||||
file_dump_json('somefile', [1, 2, 3])
|
||||
assert file_open.call_count == 1
|
||||
assert json_dump.call_count == 1
|
||||
file_open = mocker.patch('freqtrade.misc.gzip.open', MagicMock())
|
||||
json_dump = mocker.patch('json.dump', MagicMock())
|
||||
file_dump_json('somefile', [1, 2, 3], True)
|
||||
assert file_open.call_count == 1
|
||||
assert json_dump.call_count == 1
|
||||
|
||||
|
||||
def test_format_ms_time() -> None:
|
||||
"""
|
||||
test format_ms_time()
|
||||
:return: None
|
||||
"""
|
||||
# Date 2018-04-10 18:02:01
|
||||
date_in_epoch_ms = 1523383321000
|
||||
date = format_ms_time(date_in_epoch_ms)
|
||||
assert type(date) is str
|
||||
res = datetime.datetime(2018, 4, 10, 18, 2, 1, tzinfo=datetime.timezone.utc)
|
||||
assert date == res.astimezone(None).strftime('%Y-%m-%dT%H:%M:%S')
|
||||
res = datetime.datetime(2017, 12, 13, 8, 2, 1, tzinfo=datetime.timezone.utc)
|
||||
# Date 2017-12-13 08:02:01
|
||||
date_in_epoch_ms = 1513152121000
|
||||
assert format_ms_time(date_in_epoch_ms) == res.astimezone(None).strftime('%Y-%m-%dT%H:%M:%S')
|
||||
|
@@ -4,7 +4,6 @@ import os
|
||||
import pytest
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
from freqtrade.exchange import Exchanges
|
||||
from freqtrade.persistence import Trade, init, clean_dry_run_db
|
||||
|
||||
|
||||
@@ -96,7 +95,7 @@ def test_init_prod_db(default_conf, mocker):
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_update_with_bittrex(limit_buy_order, limit_sell_order):
|
||||
def test_update_with_bittrex(limit_buy_order, limit_sell_order, fee):
|
||||
"""
|
||||
On this test we will buy and sell a crypto currency.
|
||||
|
||||
@@ -125,10 +124,11 @@ def test_update_with_bittrex(limit_buy_order, limit_sell_order):
|
||||
"""
|
||||
|
||||
trade = Trade(
|
||||
pair='BTC_ETH',
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
fee=0.0025,
|
||||
exchange=Exchanges.BITTREX,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
)
|
||||
assert trade.open_order_id is None
|
||||
assert trade.open_rate is None
|
||||
@@ -151,12 +151,13 @@ def test_update_with_bittrex(limit_buy_order, limit_sell_order):
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order):
|
||||
def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee):
|
||||
trade = Trade(
|
||||
pair='BTC_ETH',
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
fee=0.0025,
|
||||
exchange=Exchanges.BITTREX,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
)
|
||||
|
||||
trade.open_order_id = 'something'
|
||||
@@ -174,12 +175,13 @@ def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order):
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_close_trade_price_exception(limit_buy_order):
|
||||
def test_calc_close_trade_price_exception(limit_buy_order, fee):
|
||||
trade = Trade(
|
||||
pair='BTC_ETH',
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
fee=0.0025,
|
||||
exchange=Exchanges.BITTREX,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
)
|
||||
|
||||
trade.open_order_id = 'something'
|
||||
@@ -190,10 +192,11 @@ def test_calc_close_trade_price_exception(limit_buy_order):
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_update_open_order(limit_buy_order):
|
||||
trade = Trade(
|
||||
pair='BTC_ETH',
|
||||
pair='ETH/BTC',
|
||||
stake_amount=1.00,
|
||||
fee=0.1,
|
||||
exchange=Exchanges.BITTREX,
|
||||
fee_open=0.1,
|
||||
fee_close=0.1,
|
||||
exchange='bittrex',
|
||||
)
|
||||
|
||||
assert trade.open_order_id is None
|
||||
@@ -201,7 +204,7 @@ def test_update_open_order(limit_buy_order):
|
||||
assert trade.close_profit is None
|
||||
assert trade.close_date is None
|
||||
|
||||
limit_buy_order['closed'] = False
|
||||
limit_buy_order['status'] = 'open'
|
||||
trade.update(limit_buy_order)
|
||||
|
||||
assert trade.open_order_id is None
|
||||
@@ -213,10 +216,11 @@ def test_update_open_order(limit_buy_order):
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_update_invalid_order(limit_buy_order):
|
||||
trade = Trade(
|
||||
pair='BTC_ETH',
|
||||
pair='ETH/BTC',
|
||||
stake_amount=1.00,
|
||||
fee=0.1,
|
||||
exchange=Exchanges.BITTREX,
|
||||
fee_open=0.1,
|
||||
fee_close=0.1,
|
||||
exchange='bittrex',
|
||||
)
|
||||
limit_buy_order['type'] = 'invalid'
|
||||
with pytest.raises(ValueError, match=r'Unknown order type'):
|
||||
@@ -224,12 +228,13 @@ def test_update_invalid_order(limit_buy_order):
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_open_trade_price(limit_buy_order):
|
||||
def test_calc_open_trade_price(limit_buy_order, fee):
|
||||
trade = Trade(
|
||||
pair='BTC_ETH',
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
fee=0.0025,
|
||||
exchange=Exchanges.BITTREX,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
)
|
||||
trade.open_order_id = 'open_trade'
|
||||
trade.update(limit_buy_order) # Buy @ 0.00001099
|
||||
@@ -242,12 +247,13 @@ def test_calc_open_trade_price(limit_buy_order):
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_close_trade_price(limit_buy_order, limit_sell_order):
|
||||
def test_calc_close_trade_price(limit_buy_order, limit_sell_order, fee):
|
||||
trade = Trade(
|
||||
pair='BTC_ETH',
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
fee=0.0025,
|
||||
exchange=Exchanges.BITTREX,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
)
|
||||
trade.open_order_id = 'close_trade'
|
||||
trade.update(limit_buy_order) # Buy @ 0.00001099
|
||||
@@ -264,12 +270,13 @@ def test_calc_close_trade_price(limit_buy_order, limit_sell_order):
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_profit(limit_buy_order, limit_sell_order):
|
||||
def test_calc_profit(limit_buy_order, limit_sell_order, fee):
|
||||
trade = Trade(
|
||||
pair='BTC_ETH',
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
fee=0.0025,
|
||||
exchange=Exchanges.BITTREX,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
)
|
||||
trade.open_order_id = 'profit_percent'
|
||||
trade.update(limit_buy_order) # Buy @ 0.00001099
|
||||
@@ -295,12 +302,13 @@ def test_calc_profit(limit_buy_order, limit_sell_order):
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_profit_percent(limit_buy_order, limit_sell_order):
|
||||
def test_calc_profit_percent(limit_buy_order, limit_sell_order, fee):
|
||||
trade = Trade(
|
||||
pair='BTC_ETH',
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
fee=0.0025,
|
||||
exchange=Exchanges.BITTREX,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
)
|
||||
trade.open_order_id = 'profit_percent'
|
||||
trade.update(limit_buy_order) # Buy @ 0.00001099
|
||||
@@ -319,40 +327,43 @@ def test_calc_profit_percent(limit_buy_order, limit_sell_order):
|
||||
assert trade.calc_profit_percent(fee=0.003) == 0.0614782
|
||||
|
||||
|
||||
def test_clean_dry_run_db(default_conf):
|
||||
def test_clean_dry_run_db(default_conf, fee):
|
||||
init(default_conf, create_engine('sqlite://'))
|
||||
|
||||
# Simulate dry_run entries
|
||||
trade = Trade(
|
||||
pair='BTC_ETH',
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
amount=123.0,
|
||||
fee=0.0025,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_rate=0.123,
|
||||
exchange='BITTREX',
|
||||
exchange='bittrex',
|
||||
open_order_id='dry_run_buy_12345'
|
||||
)
|
||||
Trade.session.add(trade)
|
||||
|
||||
trade = Trade(
|
||||
pair='BTC_ETC',
|
||||
pair='ETC/BTC',
|
||||
stake_amount=0.001,
|
||||
amount=123.0,
|
||||
fee=0.0025,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_rate=0.123,
|
||||
exchange='BITTREX',
|
||||
exchange='bittrex',
|
||||
open_order_id='dry_run_sell_12345'
|
||||
)
|
||||
Trade.session.add(trade)
|
||||
|
||||
# Simulate prod entry
|
||||
trade = Trade(
|
||||
pair='BTC_ETC',
|
||||
pair='ETC/BTC',
|
||||
stake_amount=0.001,
|
||||
amount=123.0,
|
||||
fee=0.0025,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_rate=0.123,
|
||||
exchange='BITTREX',
|
||||
exchange='bittrex',
|
||||
open_order_id='prod_buy_12345'
|
||||
)
|
||||
Trade.session.add(trade)
|
||||
@@ -364,3 +375,105 @@ def test_clean_dry_run_db(default_conf):
|
||||
|
||||
# We have now only the prod
|
||||
assert len(Trade.query.filter(Trade.open_order_id.isnot(None)).all()) == 1
|
||||
|
||||
|
||||
def test_migrate_old(default_conf, fee):
|
||||
"""
|
||||
Test Database migration(starting with old pairformat)
|
||||
"""
|
||||
amount = 103.223
|
||||
create_table_old = """CREATE TABLE IF NOT EXISTS "trades" (
|
||||
id INTEGER NOT NULL,
|
||||
exchange VARCHAR NOT NULL,
|
||||
pair VARCHAR NOT NULL,
|
||||
is_open BOOLEAN NOT NULL,
|
||||
fee FLOAT NOT NULL,
|
||||
open_rate FLOAT,
|
||||
close_rate FLOAT,
|
||||
close_profit FLOAT,
|
||||
stake_amount FLOAT NOT NULL,
|
||||
amount FLOAT,
|
||||
open_date DATETIME NOT NULL,
|
||||
close_date DATETIME,
|
||||
open_order_id VARCHAR,
|
||||
PRIMARY KEY (id),
|
||||
CHECK (is_open IN (0, 1))
|
||||
);"""
|
||||
insert_table_old = """INSERT INTO trades (exchange, pair, is_open, fee,
|
||||
open_rate, stake_amount, amount, open_date)
|
||||
VALUES ('BITTREX', 'BTC_ETC', 1, {fee},
|
||||
0.00258580, {stake}, {amount},
|
||||
'2017-11-28 12:44:24.000000')
|
||||
""".format(fee=fee.return_value,
|
||||
stake=default_conf.get("stake_amount"),
|
||||
amount=amount
|
||||
)
|
||||
engine = create_engine('sqlite://')
|
||||
# Create table using the old format
|
||||
engine.execute(create_table_old)
|
||||
engine.execute(insert_table_old)
|
||||
# Run init to test migration
|
||||
init(default_conf, engine)
|
||||
|
||||
assert len(Trade.query.filter(Trade.id == 1).all()) == 1
|
||||
trade = Trade.query.filter(Trade.id == 1).first()
|
||||
assert trade.fee_open == fee.return_value
|
||||
assert trade.fee_close == fee.return_value
|
||||
assert trade.open_rate_requested is None
|
||||
assert trade.close_rate_requested is None
|
||||
assert trade.is_open == 1
|
||||
assert trade.amount == amount
|
||||
assert trade.stake_amount == default_conf.get("stake_amount")
|
||||
assert trade.pair == "ETC/BTC"
|
||||
assert trade.exchange == "bittrex"
|
||||
|
||||
|
||||
def test_migrate_new(default_conf, fee):
|
||||
"""
|
||||
Test Database migration (starting with new pairformat)
|
||||
"""
|
||||
amount = 103.223
|
||||
create_table_old = """CREATE TABLE IF NOT EXISTS "trades" (
|
||||
id INTEGER NOT NULL,
|
||||
exchange VARCHAR NOT NULL,
|
||||
pair VARCHAR NOT NULL,
|
||||
is_open BOOLEAN NOT NULL,
|
||||
fee FLOAT NOT NULL,
|
||||
open_rate FLOAT,
|
||||
close_rate FLOAT,
|
||||
close_profit FLOAT,
|
||||
stake_amount FLOAT NOT NULL,
|
||||
amount FLOAT,
|
||||
open_date DATETIME NOT NULL,
|
||||
close_date DATETIME,
|
||||
open_order_id VARCHAR,
|
||||
PRIMARY KEY (id),
|
||||
CHECK (is_open IN (0, 1))
|
||||
);"""
|
||||
insert_table_old = """INSERT INTO trades (exchange, pair, is_open, fee,
|
||||
open_rate, stake_amount, amount, open_date)
|
||||
VALUES ('binance', 'ETC/BTC', 1, {fee},
|
||||
0.00258580, {stake}, {amount},
|
||||
'2019-11-28 12:44:24.000000')
|
||||
""".format(fee=fee.return_value,
|
||||
stake=default_conf.get("stake_amount"),
|
||||
amount=amount
|
||||
)
|
||||
engine = create_engine('sqlite://')
|
||||
# Create table using the old format
|
||||
engine.execute(create_table_old)
|
||||
engine.execute(insert_table_old)
|
||||
# Run init to test migration
|
||||
init(default_conf, engine)
|
||||
|
||||
assert len(Trade.query.filter(Trade.id == 1).all()) == 1
|
||||
trade = Trade.query.filter(Trade.id == 1).first()
|
||||
assert trade.fee_open == fee.return_value
|
||||
assert trade.fee_close == fee.return_value
|
||||
assert trade.open_rate_requested is None
|
||||
assert trade.close_rate_requested is None
|
||||
assert trade.is_open == 1
|
||||
assert trade.amount == amount
|
||||
assert trade.stake_amount == default_conf.get("stake_amount")
|
||||
assert trade.pair == "ETC/BTC"
|
||||
assert trade.exchange == "binance"
|
||||
|
1
freqtrade/tests/testdata/ADA_BTC-1m.json
vendored
Normal file
1
freqtrade/tests/testdata/ADA_BTC-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/ADA_BTC-5m.json
vendored
Normal file
1
freqtrade/tests/testdata/ADA_BTC-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_ADA-5.json
vendored
1
freqtrade/tests/testdata/BTC_ADA-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_DASH-5.json
vendored
1
freqtrade/tests/testdata/BTC_DASH-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_ETC-5.json
vendored
1
freqtrade/tests/testdata/BTC_ETC-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_ETH-5.json
vendored
1
freqtrade/tests/testdata/BTC_ETH-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_LTC-5.json
vendored
1
freqtrade/tests/testdata/BTC_LTC-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_NXT-5.json
vendored
1
freqtrade/tests/testdata/BTC_NXT-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_POWR-5.json
vendored
1
freqtrade/tests/testdata/BTC_POWR-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_XLM-5.json
vendored
1
freqtrade/tests/testdata/BTC_XLM-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_XMR-5.json
vendored
1
freqtrade/tests/testdata/BTC_XMR-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_ZEC-5.json
vendored
1
freqtrade/tests/testdata/BTC_ZEC-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/DASH_BTC-1m.json
vendored
Normal file
1
freqtrade/tests/testdata/DASH_BTC-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/DASH_BTC-5m.json
vendored
Normal file
1
freqtrade/tests/testdata/DASH_BTC-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/ETC_BTC-1m.json
vendored
Normal file
1
freqtrade/tests/testdata/ETC_BTC-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/ETC_BTC-5m.json
vendored
Normal file
1
freqtrade/tests/testdata/ETC_BTC-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/ETH_BTC-1m.json
vendored
Normal file
1
freqtrade/tests/testdata/ETH_BTC-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/ETH_BTC-5m.json
vendored
Normal file
1
freqtrade/tests/testdata/ETH_BTC-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/LTC_BTC-1m.json
vendored
Normal file
1
freqtrade/tests/testdata/LTC_BTC-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/LTC_BTC-5m.json
vendored
Normal file
1
freqtrade/tests/testdata/LTC_BTC-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/NXT_BTC-1m.json
vendored
Normal file
1
freqtrade/tests/testdata/NXT_BTC-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/NXT_BTC-5m.json
vendored
Normal file
1
freqtrade/tests/testdata/NXT_BTC-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/POWR_BTC-1m.json
vendored
Normal file
1
freqtrade/tests/testdata/POWR_BTC-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/POWR_BTC-5m.json
vendored
Normal file
1
freqtrade/tests/testdata/POWR_BTC-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/UNITTEST_BTC-1m.json
vendored
Normal file
1
freqtrade/tests/testdata/UNITTEST_BTC-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/UNITTEST_BTC-30m.json
vendored
Normal file
1
freqtrade/tests/testdata/UNITTEST_BTC-30m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/UNITTEST_BTC-5m.json
vendored
Normal file
1
freqtrade/tests/testdata/UNITTEST_BTC-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
0
freqtrade/tests/testdata/UNITTEST_BTC-8m.json
vendored
Normal file
0
freqtrade/tests/testdata/UNITTEST_BTC-8m.json
vendored
Normal file
BIN
freqtrade/tests/testdata/UNITTEST_BTC-8m.json.gz
vendored
Normal file
BIN
freqtrade/tests/testdata/UNITTEST_BTC-8m.json.gz
vendored
Normal file
Binary file not shown.
1
freqtrade/tests/testdata/XLM_BTC-1m.json
vendored
Normal file
1
freqtrade/tests/testdata/XLM_BTC-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/XLM_BTC-5m.json
vendored
Normal file
1
freqtrade/tests/testdata/XLM_BTC-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/XMR_BTC-1m.json
vendored
Normal file
1
freqtrade/tests/testdata/XMR_BTC-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/XMR_BTC-5m.json
vendored
Normal file
1
freqtrade/tests/testdata/XMR_BTC-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/ZEC_BTC-1m.json
vendored
Normal file
1
freqtrade/tests/testdata/ZEC_BTC-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/ZEC_BTC-5m.json
vendored
Normal file
1
freqtrade/tests/testdata/ZEC_BTC-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""This script generate json data from bittrex"""
|
||||
import json
|
||||
import sys
|
||||
|
||||
from freqtrade import exchange
|
||||
from freqtrade import misc
|
||||
from freqtrade.exchange import Bittrex
|
||||
|
||||
parser = misc.common_args_parser('download utility')
|
||||
parser.add_argument(
|
||||
'-p', '--pair',
|
||||
help='JSON file containing pairs to download',
|
||||
dest='pair',
|
||||
default=None
|
||||
)
|
||||
args = parser.parse_args(sys.argv[1:])
|
||||
|
||||
TICKER_INTERVALS = [1, 5] # ticker interval in minutes (currently implemented: 1 and 5)
|
||||
PAIRS = []
|
||||
|
||||
if args.pair:
|
||||
with open(args.pair) as file:
|
||||
PAIRS = json.load(file)
|
||||
PAIRS = list(set(PAIRS))
|
||||
|
||||
print('About to download pairs:', PAIRS)
|
||||
|
||||
# Init Bittrex exchange
|
||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
||||
|
||||
for pair in PAIRS:
|
||||
for tick_interval in TICKER_INTERVALS:
|
||||
print('downloading pair %s, interval %s' % (pair, tick_interval))
|
||||
data = exchange.get_ticker_history(pair, tick_interval)
|
||||
filename = '{}-{}.json'.format(pair, tick_interval)
|
||||
misc.file_dump_json(filename, data)
|
46
freqtrade/tests/testdata/pairs.json
vendored
46
freqtrade/tests/testdata/pairs.json
vendored
@@ -1,26 +1,26 @@
|
||||
[
|
||||
"BTC_ADA",
|
||||
"BTC_BAT",
|
||||
"BTC_DASH",
|
||||
"BTC_ETC",
|
||||
"BTC_ETH",
|
||||
"BTC_GBYTE",
|
||||
"BTC_LSK",
|
||||
"BTC_LTC",
|
||||
"BTC_NEO",
|
||||
"BTC_NXT",
|
||||
"BTC_POWR",
|
||||
"BTC_STORJ",
|
||||
"BTC_QTUM",
|
||||
"BTC_WAVES",
|
||||
"BTC_VTC",
|
||||
"BTC_XLM",
|
||||
"BTC_XMR",
|
||||
"BTC_XVG",
|
||||
"BTC_XRP",
|
||||
"BTC_ZEC",
|
||||
"USDT_BTC",
|
||||
"USDT_LTC",
|
||||
"USDT_ETH"
|
||||
"ADA/BTC",
|
||||
"BAT/BTC",
|
||||
"DASH/BTC",
|
||||
"ETC/BTC",
|
||||
"ETH/BTC",
|
||||
"GBYTE/BTC",
|
||||
"LSK/BTC",
|
||||
"LTC/BTC",
|
||||
"NEO/BTC",
|
||||
"NXT/BTC",
|
||||
"POWR/BTC",
|
||||
"STORJ/BTC",
|
||||
"QTUM/BTC",
|
||||
"WAVES/BTC",
|
||||
"VTC/BTC",
|
||||
"XLM/BTC",
|
||||
"XMR/BTC",
|
||||
"XVG/BTC",
|
||||
"XRP/BTC",
|
||||
"ZEC/BTC",
|
||||
"BTC/USDT",
|
||||
"LTC/USDT",
|
||||
"ETH/USDT"
|
||||
]
|
||||
|
||||
|
Reference in New Issue
Block a user