Merge branch 'develop' into ta_on_candle

This commit is contained in:
Matthias
2018-09-01 20:01:18 +02:00
30 changed files with 856 additions and 235 deletions

View File

@@ -102,7 +102,18 @@ def default_conf():
"sell": 30
},
"bid_strategy": {
"ask_last_balance": 0.0
"ask_last_balance": 0.0,
"use_order_book": False,
"order_book_top": 1,
"check_depth_of_market": {
"enabled": False,
"bids_to_ask_delta": 1
}
},
"ask_strategy": {
"use_order_book": False,
"order_book_min": 1,
"order_book_max": 1
},
"exchange": {
"name": "bittrex",
@@ -403,6 +414,39 @@ def limit_sell_order():
}
@pytest.fixture
def order_book_l2():
return MagicMock(return_value={
'bids': [
[0.043936, 10.442],
[0.043935, 31.865],
[0.043933, 11.212],
[0.043928, 0.088],
[0.043925, 10.0],
[0.043921, 10.0],
[0.04392, 37.64],
[0.043899, 0.066],
[0.043885, 0.676],
[0.04387, 22.758]
],
'asks': [
[0.043949, 0.346],
[0.04395, 0.608],
[0.043951, 3.948],
[0.043954, 0.288],
[0.043958, 9.277],
[0.043995, 1.566],
[0.044, 0.588],
[0.044002, 0.992],
[0.044003, 0.095],
[0.04402, 37.64]
],
'timestamp': None,
'datetime': None,
'nonce': 288004540
})
@pytest.fixture
def ticker_history():
return [

View File

@@ -515,6 +515,35 @@ def test_get_ticker(default_conf, mocker):
exchange.get_ticker(pair='ETH/BTC', refresh=True)
def test_get_order_book(default_conf, mocker, order_book_l2):
default_conf['exchange']['name'] = 'binance'
api_mock = MagicMock()
api_mock.fetch_l2_order_book = order_book_l2
exchange = get_patched_exchange(mocker, default_conf, api_mock)
order_book = exchange.get_order_book(pair='ETH/BTC', limit=10)
assert 'bids' in order_book
assert 'asks' in order_book
assert len(order_book['bids']) == 10
assert len(order_book['asks']) == 10
def test_get_order_book_exception(default_conf, mocker):
api_mock = MagicMock()
with pytest.raises(OperationalException):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NotSupported)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_order_book(pair='ETH/BTC', limit=50)
with pytest.raises(TemporaryError):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NetworkError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_order_book(pair='ETH/BTC', limit=50)
with pytest.raises(OperationalException):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.BaseError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_order_book(pair='ETH/BTC', limit=50)
def make_fetch_ohlcv_mock(data):
def fetch_ohlcv_mock(pair, timeframe, since):
if since:
@@ -823,15 +852,3 @@ def test_get_fee(default_conf, mocker):
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
'get_fee', 'calculate_fee')
def test_get_amount_lots(default_conf, mocker):
api_mock = MagicMock()
api_mock.amount_to_lots = MagicMock(return_value=1.0)
api_mock.markets = None
marketmock = MagicMock()
api_mock.load_markets = marketmock
exchange = get_patched_exchange(mocker, default_conf, api_mock)
assert exchange.get_amount_lots('LTC/BTC', 1.54) == 1
assert marketmock.call_count == 1

View File

@@ -6,6 +6,7 @@ from unittest.mock import MagicMock, ANY
import pytest
from freqtrade import TemporaryError
from freqtrade.fiat_convert import CryptoToFiatConverter
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import Trade
@@ -285,11 +286,12 @@ def test_rpc_balance_handle(default_conf, mocker):
'used': 2.0,
},
'ETH': {
'free': 0.0,
'total': 0.0,
'used': 0.0,
'free': 1.0,
'total': 5.0,
'used': 4.0,
}
}
# ETH will be skipped due to mocked Error below
mocker.patch.multiple(
'freqtrade.fiat_convert.Market',
@@ -301,7 +303,8 @@ def test_rpc_balance_handle(default_conf, mocker):
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_balances=MagicMock(return_value=mock_balance)
get_balances=MagicMock(return_value=mock_balance),
get_ticker=MagicMock(side_effect=TemporaryError('Could not load ticker due to xxx'))
)
freqtradebot = FreqtradeBot(default_conf)
@@ -320,6 +323,7 @@ def test_rpc_balance_handle(default_conf, mocker):
'pending': 2.0,
'est_btc': 12.0,
}]
assert result['total'] == 12.0
def test_rpc_start(mocker, default_conf) -> None:

View File

@@ -1095,6 +1095,38 @@ def test_send_msg_status_notification(default_conf, mocker) -> None:
assert msg_mock.call_args[0][0] == '*Status:* `running`'
def test_warning_notification(default_conf, mocker) -> None:
msg_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
_send_msg=msg_mock
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot)
telegram.send_msg({
'type': RPCMessageType.WARNING_NOTIFICATION,
'status': 'message'
})
assert msg_mock.call_args[0][0] == '*Warning:* `message`'
def test_custom_notification(default_conf, mocker) -> None:
msg_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
_send_msg=msg_mock
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot)
telegram.send_msg({
'type': RPCMessageType.CUSTOM_NOTIFICATION,
'status': '*Custom:* `Hello World`'
})
assert msg_mock.call_args[0][0] == '*Custom:* `Hello World`'
def test_send_msg_unknown_type(default_conf, mocker) -> None:
msg_mock = MagicMock()
mocker.patch.multiple(

View File

@@ -8,6 +8,7 @@ from pandas import DataFrame
from freqtrade.arguments import TimeRange
from freqtrade.optimize.__init__ import load_tickerdata_file
from freqtrade.persistence import Trade
from freqtrade.tests.conftest import get_patched_exchange, log_has
from freqtrade.strategy.default_strategy import DefaultStrategy
@@ -107,6 +108,29 @@ def test_tickerdata_to_dataframe(default_conf) -> None:
assert len(data['UNITTEST/BTC']) == 99 # partial candle was removed
def test_min_roi_reached(default_conf, fee) -> None:
strategy = DefaultStrategy(default_conf)
strategy.minimal_roi = {0: 0.1, 20: 0.05, 55: 0.01}
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
open_date=arrow.utcnow().shift(hours=-1).datetime,
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange='bittrex',
open_rate=1,
)
assert not strategy.min_roi_reached(trade, 0.01, arrow.utcnow().shift(minutes=-55).datetime)
assert strategy.min_roi_reached(trade, 0.12, arrow.utcnow().shift(minutes=-55).datetime)
assert not strategy.min_roi_reached(trade, 0.04, arrow.utcnow().shift(minutes=-39).datetime)
assert strategy.min_roi_reached(trade, 0.06, arrow.utcnow().shift(minutes=-39).datetime)
assert not strategy.min_roi_reached(trade, -0.01, arrow.utcnow().shift(minutes=-1).datetime)
assert strategy.min_roi_reached(trade, 0.02, arrow.utcnow().shift(minutes=-1).datetime)
def test_analyze_ticker_default(ticker_history, mocker, caplog) -> None:
caplog.set_level(logging.DEBUG)
ind_mock = MagicMock(side_effect=lambda x, meta: x)

View File

@@ -130,7 +130,7 @@ def test_strategy_override_minimal_roi(caplog):
assert resolver.strategy.minimal_roi[0] == 0.5
assert ('freqtrade.strategy.resolver',
logging.INFO,
"Override strategy 'minimal_roi' with value in config file."
"Override strategy 'minimal_roi' with value in config file: {'0': 0.5}."
) in caplog.record_tuples

View File

@@ -159,6 +159,15 @@ def test_gen_pair_whitelist(mocker, default_conf, tickers) -> None:
assert whitelist == []
def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers)
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=False))
with pytest.raises(OperationalException):
freqtrade._gen_pair_whitelist(base_currency='BTC')
@pytest.mark.skip(reason="Test not implemented")
def test_refresh_whitelist() -> None:
pass
@@ -664,21 +673,21 @@ def test_balance_fully_ask_side(mocker, default_conf) -> None:
default_conf['bid_strategy']['ask_last_balance'] = 0.0
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert freqtrade.get_target_bid({'ask': 20, 'last': 10}) == 20
assert freqtrade.get_target_bid('ETH/BTC', {'ask': 20, 'last': 10}) == 20
def test_balance_fully_last_side(mocker, default_conf) -> None:
default_conf['bid_strategy']['ask_last_balance'] = 1.0
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert freqtrade.get_target_bid({'ask': 20, 'last': 10}) == 10
assert freqtrade.get_target_bid('ETH/BTC', {'ask': 20, 'last': 10}) == 10
def test_balance_bigger_last_ask(mocker, default_conf) -> None:
default_conf['bid_strategy']['ask_last_balance'] = 1.0
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert freqtrade.get_target_bid({'ask': 5, 'last': 10}) == 5
assert freqtrade.get_target_bid('ETH/BTC', {'ask': 5, 'last': 10}) == 5
def test_process_maybe_execute_buy(mocker, default_conf) -> None:
@@ -1876,3 +1885,194 @@ def test_get_real_amount_open_trade(default_conf, mocker):
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
assert freqtrade.get_real_amount(trade, order) == amount
def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order, fee, markets, mocker,
order_book_l2):
default_conf['bid_strategy']['check_depth_of_market']['enabled'] = True
default_conf['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = 0.1
patch_RPCManager(mocker)
mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
get_markets=markets
)
# Save state of current whitelist
whitelist = deepcopy(default_conf['exchange']['pair_whitelist'])
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
freqtrade.create_trade()
trade = Trade.query.first()
assert trade is not None
assert trade.stake_amount == 0.001
assert trade.is_open
assert trade.open_date is not None
assert trade.exchange == 'bittrex'
# Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_buy_order)
assert trade.open_rate == 0.00001099
assert whitelist == default_conf['exchange']['pair_whitelist']
def test_order_book_depth_of_market_high_delta(default_conf, ticker, limit_buy_order,
fee, markets, mocker, order_book_l2):
default_conf['bid_strategy']['check_depth_of_market']['enabled'] = True
# delta is 100 which is impossible to reach. hence check_depth_of_market will return false
default_conf['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = 100
patch_RPCManager(mocker)
mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
get_markets=markets
)
# Save state of current whitelist
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
freqtrade.create_trade()
trade = Trade.query.first()
assert trade is None
def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2, markets) -> None:
"""
test if function get_target_bid will return the order book price
instead of the ask rate
"""
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_markets=markets,
get_order_book=order_book_l2
)
default_conf['exchange']['name'] = 'binance'
default_conf['bid_strategy']['use_order_book'] = True
default_conf['bid_strategy']['order_book_top'] = 2
default_conf['bid_strategy']['ask_last_balance'] = 0
default_conf['telegram']['enabled'] = False
freqtrade = FreqtradeBot(default_conf)
assert freqtrade.get_target_bid('ETH/BTC', {'ask': 0.045, 'last': 0.046}) == 0.043935
def test_order_book_bid_strategy2(mocker, default_conf, order_book_l2, markets) -> None:
"""
test if function get_target_bid will return the ask rate (since its value is lower)
instead of the order book rate (even if enabled)
"""
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_markets=markets,
get_order_book=order_book_l2
)
default_conf['exchange']['name'] = 'binance'
default_conf['bid_strategy']['use_order_book'] = True
default_conf['bid_strategy']['order_book_top'] = 2
default_conf['bid_strategy']['ask_last_balance'] = 0
default_conf['telegram']['enabled'] = False
freqtrade = FreqtradeBot(default_conf)
assert freqtrade.get_target_bid('ETH/BTC', {'ask': 0.042, 'last': 0.046}) == 0.042
def test_order_book_bid_strategy3(default_conf, mocker, order_book_l2, markets) -> None:
"""
test if function get_target_bid will return ask rate instead
of the order book rate
"""
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_markets=markets,
get_order_book=order_book_l2
)
default_conf['exchange']['name'] = 'binance'
default_conf['bid_strategy']['use_order_book'] = True
default_conf['bid_strategy']['order_book_top'] = 1
default_conf['bid_strategy']['ask_last_balance'] = 0
default_conf['telegram']['enabled'] = False
freqtrade = FreqtradeBot(default_conf)
assert freqtrade.get_target_bid('ETH/BTC', {'ask': 0.03, 'last': 0.029}) == 0.03
def test_check_depth_of_market_buy(default_conf, mocker, order_book_l2, markets) -> None:
"""
test check depth of market
"""
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_markets=markets,
get_order_book=order_book_l2
)
default_conf['telegram']['enabled'] = False
default_conf['exchange']['name'] = 'binance'
default_conf['bid_strategy']['check_depth_of_market']['enabled'] = True
# delta is 100 which is impossible to reach. hence function will return false
default_conf['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = 100
freqtrade = FreqtradeBot(default_conf)
conf = default_conf['bid_strategy']['check_depth_of_market']
assert freqtrade._check_depth_of_market_buy('ETH/BTC', conf) is False
def test_order_book_ask_strategy(default_conf, limit_buy_order, limit_sell_order,
fee, markets, mocker, order_book_l2) -> None:
"""
test order book ask strategy
"""
mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2)
default_conf['exchange']['name'] = 'binance'
default_conf['ask_strategy']['use_order_book'] = True
default_conf['ask_strategy']['order_book_min'] = 1
default_conf['ask_strategy']['order_book_max'] = 2
default_conf['telegram']['enabled'] = False
patch_RPCManager(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=MagicMock(return_value={
'bid': 0.00001172,
'ask': 0.00001173,
'last': 0.00001172
}),
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
get_fee=fee,
get_markets=markets
)
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
freqtrade.create_trade()
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(freqtrade, value=(False, True))
assert freqtrade.handle_trade(trade) is True
def test_startup_messages(default_conf, mocker):
default_conf['dynamic_whitelist'] = 20
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert freqtrade.state is State.RUNNING

View File

@@ -1,5 +1,6 @@
# pragma pylint: disable=missing-docstring, C0103
from unittest.mock import MagicMock
import logging
import pytest
from sqlalchemy import create_engine
@@ -403,7 +404,9 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
"""
Test Database migration (starting with new pairformat)
"""
caplog.set_level(logging.DEBUG)
amount = 103.223
# Always create all columns apart from the last!
create_table_old = """CREATE TABLE IF NOT EXISTS "trades" (
id INTEGER NOT NULL,
exchange VARCHAR NOT NULL,
@@ -418,14 +421,21 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
open_date DATETIME NOT NULL,
close_date DATETIME,
open_order_id VARCHAR,
stop_loss FLOAT,
initial_stop_loss FLOAT,
max_rate FLOAT,
sell_reason VARCHAR,
strategy 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)
open_rate, stake_amount, amount, open_date,
stop_loss, initial_stop_loss, max_rate)
VALUES ('binance', 'ETC/BTC', 1, {fee},
0.00258580, {stake}, {amount},
'2019-11-28 12:44:24.000000')
'2019-11-28 12:44:24.000000',
0.0, 0.0, 0.0)
""".format(fee=fee.return_value,
stake=default_conf.get("stake_amount"),
amount=amount
@@ -463,12 +473,15 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
assert trade.ticker_interval is None
assert log_has("trying trades_bak1", caplog.record_tuples)
assert log_has("trying trades_bak2", caplog.record_tuples)
assert log_has("Running database migration - backup available as trades_bak2",
caplog.record_tuples)
def test_migrate_mid_state(mocker, default_conf, fee, caplog):
"""
Test Database migration (starting with new pairformat)
"""
caplog.set_level(logging.DEBUG)
amount = 103.223
create_table_old = """CREATE TABLE IF NOT EXISTS "trades" (
id INTEGER NOT NULL,
@@ -522,6 +535,8 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog):
assert trade.stop_loss == 0.0
assert trade.initial_stop_loss == 0.0
assert log_has("trying trades_bak0", caplog.record_tuples)
assert log_has("Running database migration - backup available as trades_bak0",
caplog.record_tuples)
def test_adjust_stop_loss(limit_buy_order, limit_sell_order, fee):

View File

@@ -0,0 +1,16 @@
import talib.abstract as ta
import pandas as pd
def test_talib_bollingerbands_near_zero_values():
inputs = pd.DataFrame([
{'close': 0.00000010},
{'close': 0.00000011},
{'close': 0.00000012},
{'close': 0.00000013},
{'close': 0.00000014}
])
bollinger = ta.BBANDS(inputs, matype=0, timeperiod=2)
assert (bollinger['upperband'][3] != bollinger['middleband'][3])