Merge branch 'develop' into pr/imxuwang/3799

This commit is contained in:
Matthias
2020-10-22 07:55:48 +02:00
174 changed files with 4148 additions and 1648 deletions

View File

@@ -4,10 +4,8 @@ from unittest.mock import MagicMock
import pytest
import rapidjson
from freqtrade.commands.build_config_commands import (ask_user_config,
ask_user_overwrite,
start_new_config,
validate_is_float,
from freqtrade.commands.build_config_commands import (ask_user_config, ask_user_overwrite,
start_new_config, validate_is_float,
validate_is_int)
from freqtrade.exceptions import OperationalException
from tests.conftest import get_args, log_has_re

View File

@@ -2,22 +2,21 @@ import re
from pathlib import Path
from unittest.mock import MagicMock, PropertyMock
import arrow
import pytest
from freqtrade.commands import (start_convert_data, start_create_userdir,
start_download_data, start_hyperopt_list,
start_hyperopt_show, start_list_data,
start_list_exchanges, start_list_hyperopts,
start_list_markets, start_list_strategies,
start_list_timeframes, start_new_hyperopt,
start_new_strategy, start_show_trades,
start_test_pairlist, start_trading)
from freqtrade.commands import (start_convert_data, start_create_userdir, start_download_data,
start_hyperopt_list, start_hyperopt_show, start_list_data,
start_list_exchanges, start_list_hyperopts, start_list_markets,
start_list_strategies, start_list_timeframes, start_new_hyperopt,
start_new_strategy, start_show_trades, start_test_pairlist,
start_trading)
from freqtrade.configuration import setup_utils_configuration
from freqtrade.exceptions import OperationalException
from freqtrade.state import RunMode
from tests.conftest import (create_mock_trades, get_args, log_has, log_has_re,
patch_exchange,
from tests.conftest import (create_mock_trades, get_args, log_has, log_has_re, patch_exchange,
patched_configuration_load_config_file)
from tests.conftest_trades import MOCK_TRADE_COUNT
def test_setup_utils_configuration():
@@ -552,6 +551,50 @@ def test_download_data_keyboardInterrupt(mocker, caplog, markets):
assert dl_mock.call_count == 1
def test_download_data_timerange(mocker, caplog, markets):
dl_mock = mocker.patch('freqtrade.commands.data_commands.refresh_backtest_ohlcv_data',
MagicMock(return_value=["ETH/BTC", "XRP/BTC"]))
patch_exchange(mocker)
mocker.patch(
'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)
)
args = [
"download-data",
"--exchange", "binance",
"--pairs", "ETH/BTC", "XRP/BTC",
"--days", "20",
"--timerange", "20200101-"
]
with pytest.raises(OperationalException,
match=r"--days and --timerange are mutually.*"):
start_download_data(get_args(args))
assert dl_mock.call_count == 0
args = [
"download-data",
"--exchange", "binance",
"--pairs", "ETH/BTC", "XRP/BTC",
"--days", "20",
]
start_download_data(get_args(args))
assert dl_mock.call_count == 1
# 20days ago
days_ago = arrow.get(arrow.utcnow().shift(days=-20).date()).timestamp
assert dl_mock.call_args_list[0][1]['timerange'].startts == days_ago
dl_mock.reset_mock()
args = [
"download-data",
"--exchange", "binance",
"--pairs", "ETH/BTC", "XRP/BTC",
"--timerange", "20200101-"
]
start_download_data(get_args(args))
assert dl_mock.call_count == 1
assert dl_mock.call_args_list[0][1]['timerange'].startts == arrow.Arrow(2020, 1, 1).timestamp
def test_download_data_no_markets(mocker, caplog):
dl_mock = mocker.patch('freqtrade.commands.data_commands.refresh_backtest_ohlcv_data',
MagicMock(return_value=["ETH/BTC", "XRP/BTC"]))
@@ -1106,7 +1149,7 @@ def test_start_list_data(testdatadir, capsys):
@pytest.mark.usefixtures("init_persistence")
def test_show_trades(mocker, fee, capsys, caplog):
mocker.patch("freqtrade.persistence.init")
mocker.patch("freqtrade.persistence.init_db")
create_mock_trades(fee)
args = [
"show-trades",
@@ -1116,7 +1159,7 @@ def test_show_trades(mocker, fee, capsys, caplog):
pargs = get_args(args)
pargs['config'] = None
start_show_trades(pargs)
assert log_has("Printing 4 Trades: ", caplog)
assert log_has(f"Printing {MOCK_TRADE_COUNT} Trades: ", caplog)
captured = capsys.readouterr()
assert "Trade(id=1" in captured.out
assert "Trade(id=2" in captured.out

View File

@@ -13,15 +13,18 @@ import numpy as np
import pytest
from telegram import Chat, Message, Update
from freqtrade import constants, persistence
from freqtrade import constants
from freqtrade.commands import Arguments
from freqtrade.data.converter import ohlcv_to_dataframe
from freqtrade.edge import Edge, PairInfo
from freqtrade.exchange import Exchange
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import Trade
from freqtrade.persistence import Trade, init_db
from freqtrade.resolvers import ExchangeResolver
from freqtrade.worker import Worker
from tests.conftest_trades import (mock_trade_1, mock_trade_2, mock_trade_3, mock_trade_4,
mock_trade_5, mock_trade_6)
logging.getLogger('').setLevel(logging.INFO)
@@ -128,7 +131,7 @@ def patch_freqtradebot(mocker, config) -> None:
:return: None
"""
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
persistence.init(config['db_url'])
init_db(config['db_url'])
patch_exchange(mocker)
mocker.patch('freqtrade.freqtradebot.RPCManager._init', MagicMock())
mocker.patch('freqtrade.freqtradebot.RPCManager.send_msg', MagicMock())
@@ -143,6 +146,7 @@ def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
:return: FreqtradeBot
"""
patch_freqtradebot(mocker, config)
config['datadir'] = Path(config['datadir'])
return FreqtradeBot(config)
@@ -172,64 +176,22 @@ def create_mock_trades(fee):
Create some fake trades ...
"""
# Simulate dry_run entries
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
amount=123.0,
amount_requested=123.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
exchange='bittrex',
open_order_id='dry_run_buy_12345',
strategy='DefaultStrategy',
)
trade = mock_trade_1(fee)
Trade.session.add(trade)
trade = Trade(
pair='ETC/BTC',
stake_amount=0.001,
amount=123.0,
amount_requested=123.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
close_rate=0.128,
close_profit=0.005,
exchange='bittrex',
is_open=False,
open_order_id='dry_run_sell_12345',
strategy='DefaultStrategy',
)
trade = mock_trade_2(fee)
Trade.session.add(trade)
trade = Trade(
pair='XRP/BTC',
stake_amount=0.001,
amount=123.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.05,
close_rate=0.06,
close_profit=0.01,
exchange='bittrex',
is_open=False,
)
trade = mock_trade_3(fee)
Trade.session.add(trade)
# Simulate prod entry
trade = Trade(
pair='ETC/BTC',
stake_amount=0.001,
amount=123.0,
amount_requested=124.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
exchange='bittrex',
open_order_id='prod_buy_12345',
strategy='DefaultStrategy',
)
trade = mock_trade_4(fee)
Trade.session.add(trade)
trade = mock_trade_5(fee)
Trade.session.add(trade)
trade = mock_trade_6(fee)
Trade.session.add(trade)
@@ -257,7 +219,7 @@ def patch_coingekko(mocker) -> None:
@pytest.fixture(scope='function')
def init_persistence(default_conf):
persistence.init(default_conf['db_url'], default_conf['dry_run'])
init_db(default_conf['db_url'], default_conf['dry_run'])
@pytest.fixture(scope="function")
@@ -335,7 +297,7 @@ def default_conf(testdatadir):
@pytest.fixture
def update():
_update = Update(0)
_update.message = Message(0, 0, datetime.utcnow(), Chat(0, 0))
_update.message = Message(0, datetime.utcnow(), Chat(0, 0))
return _update
@@ -823,22 +785,32 @@ def markets_empty():
@pytest.fixture(scope='function')
def limit_buy_order():
def limit_buy_order_open():
return {
'id': 'mocked_limit_buy',
'type': 'limit',
'side': 'buy',
'symbol': 'mocked',
'datetime': arrow.utcnow().isoformat(),
'timestamp': arrow.utcnow().timestamp,
'price': 0.00001099,
'amount': 90.99181073,
'filled': 90.99181073,
'filled': 0.0,
'cost': 0.0009999,
'remaining': 0.0,
'status': 'closed'
'remaining': 90.99181073,
'status': 'open'
}
@pytest.fixture(scope='function')
def limit_buy_order(limit_buy_order_open):
order = deepcopy(limit_buy_order_open)
order['status'] = 'closed'
order['filled'] = order['amount']
order['remaining'] = 0.0
return order
@pytest.fixture(scope='function')
def market_buy_order():
return {
@@ -1021,21 +993,31 @@ def limit_buy_order_canceled_empty(request):
@pytest.fixture
def limit_sell_order():
def limit_sell_order_open():
return {
'id': 'mocked_limit_sell',
'type': 'limit',
'side': 'sell',
'pair': 'mocked',
'datetime': arrow.utcnow().isoformat(),
'timestamp': arrow.utcnow().timestamp,
'price': 0.00001173,
'amount': 90.99181073,
'filled': 90.99181073,
'remaining': 0.0,
'status': 'closed'
'filled': 0.0,
'remaining': 90.99181073,
'status': 'open'
}
@pytest.fixture
def limit_sell_order(limit_sell_order_open):
order = deepcopy(limit_sell_order_open)
order['remaining'] = 0.0
order['filled'] = order['amount']
order['status'] = 'closed'
return order
@pytest.fixture
def order_book_l2():
return MagicMock(return_value={

279
tests/conftest_trades.py Normal file
View File

@@ -0,0 +1,279 @@
from freqtrade.persistence.models import Order, Trade
MOCK_TRADE_COUNT = 6
def mock_order_1():
return {
'id': '1234',
'symbol': 'ETH/BTC',
'status': 'closed',
'side': 'buy',
'type': 'limit',
'price': 0.123,
'amount': 123.0,
'filled': 123.0,
'remaining': 0.0,
}
def mock_trade_1(fee):
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
amount=123.0,
amount_requested=123.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
exchange='bittrex',
open_order_id='dry_run_buy_12345',
strategy='DefaultStrategy',
)
o = Order.parse_from_ccxt_object(mock_order_1(), 'ETH/BTC', 'buy')
trade.orders.append(o)
return trade
def mock_order_2():
return {
'id': '1235',
'symbol': 'ETC/BTC',
'status': 'closed',
'side': 'buy',
'type': 'limit',
'price': 0.123,
'amount': 123.0,
'filled': 123.0,
'remaining': 0.0,
}
def mock_order_2_sell():
return {
'id': '12366',
'symbol': 'ETC/BTC',
'status': 'closed',
'side': 'sell',
'type': 'limit',
'price': 0.128,
'amount': 123.0,
'filled': 123.0,
'remaining': 0.0,
}
def mock_trade_2(fee):
"""
Closed trade...
"""
trade = Trade(
pair='ETC/BTC',
stake_amount=0.001,
amount=123.0,
amount_requested=123.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
close_rate=0.128,
close_profit=0.005,
exchange='bittrex',
is_open=False,
open_order_id='dry_run_sell_12345',
strategy='DefaultStrategy',
)
o = Order.parse_from_ccxt_object(mock_order_2(), 'ETC/BTC', 'buy')
trade.orders.append(o)
o = Order.parse_from_ccxt_object(mock_order_2_sell(), 'ETC/BTC', 'sell')
trade.orders.append(o)
return trade
def mock_order_3():
return {
'id': '41231a12a',
'symbol': 'XRP/BTC',
'status': 'closed',
'side': 'buy',
'type': 'limit',
'price': 0.05,
'amount': 123.0,
'filled': 123.0,
'remaining': 0.0,
}
def mock_order_3_sell():
return {
'id': '41231a666a',
'symbol': 'XRP/BTC',
'status': 'closed',
'side': 'sell',
'type': 'stop_loss_limit',
'price': 0.06,
'average': 0.06,
'amount': 123.0,
'filled': 123.0,
'remaining': 0.0,
}
def mock_trade_3(fee):
"""
Closed trade
"""
trade = Trade(
pair='XRP/BTC',
stake_amount=0.001,
amount=123.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.05,
close_rate=0.06,
close_profit=0.01,
exchange='bittrex',
is_open=False,
)
o = Order.parse_from_ccxt_object(mock_order_3(), 'XRP/BTC', 'buy')
trade.orders.append(o)
o = Order.parse_from_ccxt_object(mock_order_3_sell(), 'XRP/BTC', 'sell')
trade.orders.append(o)
return trade
def mock_order_4():
return {
'id': 'prod_buy_12345',
'symbol': 'ETC/BTC',
'status': 'open',
'side': 'buy',
'type': 'limit',
'price': 0.123,
'amount': 123.0,
'filled': 0.0,
'remaining': 123.0,
}
def mock_trade_4(fee):
"""
Simulate prod entry
"""
trade = Trade(
pair='ETC/BTC',
stake_amount=0.001,
amount=123.0,
amount_requested=124.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
exchange='bittrex',
open_order_id='prod_buy_12345',
strategy='DefaultStrategy',
)
o = Order.parse_from_ccxt_object(mock_order_4(), 'ETC/BTC', 'buy')
trade.orders.append(o)
return trade
def mock_order_5():
return {
'id': 'prod_buy_3455',
'symbol': 'XRP/BTC',
'status': 'closed',
'side': 'buy',
'type': 'limit',
'price': 0.123,
'amount': 123.0,
'filled': 123.0,
'remaining': 0.0,
}
def mock_order_5_stoploss():
return {
'id': 'prod_stoploss_3455',
'symbol': 'XRP/BTC',
'status': 'open',
'side': 'sell',
'type': 'stop_loss_limit',
'price': 0.123,
'amount': 123.0,
'filled': 0.0,
'remaining': 123.0,
}
def mock_trade_5(fee):
"""
Simulate prod entry with stoploss
"""
trade = Trade(
pair='XRP/BTC',
stake_amount=0.001,
amount=123.0,
amount_requested=124.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
exchange='bittrex',
strategy='SampleStrategy',
stoploss_order_id='prod_stoploss_3455'
)
o = Order.parse_from_ccxt_object(mock_order_5(), 'XRP/BTC', 'buy')
trade.orders.append(o)
o = Order.parse_from_ccxt_object(mock_order_5_stoploss(), 'XRP/BTC', 'stoploss')
trade.orders.append(o)
return trade
def mock_order_6():
return {
'id': 'prod_buy_6',
'symbol': 'LTC/BTC',
'status': 'closed',
'side': 'buy',
'type': 'limit',
'price': 0.15,
'amount': 2.0,
'filled': 2.0,
'remaining': 0.0,
}
def mock_order_6_sell():
return {
'id': 'prod_sell_6',
'symbol': 'LTC/BTC',
'status': 'open',
'side': 'sell',
'type': 'limit',
'price': 0.20,
'amount': 2.0,
'filled': 0.0,
'remaining': 2.0,
}
def mock_trade_6(fee):
"""
Simulate prod entry with open sell order
"""
trade = Trade(
pair='LTC/BTC',
stake_amount=0.001,
amount=2.0,
amount_requested=2.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.15,
exchange='bittrex',
strategy='SampleStrategy',
open_order_id="prod_sell_6",
)
o = Order.parse_from_ccxt_object(mock_order_6(), 'LTC/BTC', 'buy')
trade.orders.append(o)
o = Order.parse_from_ccxt_object(mock_order_6_sell(), 'LTC/BTC', 'sell')
trade.orders.append(o)
return trade

View File

@@ -7,19 +7,16 @@ from pandas import DataFrame, DateOffset, Timestamp, to_datetime
from freqtrade.configuration import TimeRange
from freqtrade.constants import LAST_BT_RESULT_FN
from freqtrade.data.btanalysis import (BT_DATA_COLUMNS,
analyze_trade_parallelism,
calculate_market_change,
calculate_max_drawdown,
combine_dataframes_with_mean,
create_cum_profit,
extract_trades_of_period,
get_latest_backtest_filename,
load_backtest_data, load_trades,
from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, analyze_trade_parallelism,
calculate_market_change, calculate_max_drawdown,
combine_dataframes_with_mean, create_cum_profit,
extract_trades_of_period, get_latest_backtest_filename,
get_latest_hyperopt_file, load_backtest_data, load_trades,
load_trades_from_db)
from freqtrade.data.history import load_data, load_pair_history
from freqtrade.optimize.backtesting import BacktestResult
from tests.conftest import create_mock_trades
from tests.conftest_trades import MOCK_TRADE_COUNT
def test_get_latest_backtest_filename(testdatadir, mocker):
@@ -42,6 +39,17 @@ def test_get_latest_backtest_filename(testdatadir, mocker):
get_latest_backtest_filename(testdatadir)
def test_get_latest_hyperopt_file(testdatadir, mocker):
res = get_latest_hyperopt_file(testdatadir / 'does_not_exist', 'testfile.pickle')
assert res == testdatadir / 'does_not_exist/testfile.pickle'
res = get_latest_hyperopt_file(testdatadir.parent)
assert res == testdatadir.parent / "hyperopt_results.pickle"
res = get_latest_hyperopt_file(str(testdatadir.parent))
assert res == testdatadir.parent / "hyperopt_results.pickle"
def test_load_backtest_data_old_format(testdatadir):
filename = testdatadir / "backtest-result_test.json"
@@ -106,11 +114,11 @@ def test_load_trades_from_db(default_conf, fee, mocker):
create_mock_trades(fee)
# remove init so it does not init again
init_mock = mocker.patch('freqtrade.persistence.init', MagicMock())
init_mock = mocker.patch('freqtrade.data.btanalysis.init_db', MagicMock())
trades = load_trades_from_db(db_url=default_conf['db_url'])
assert init_mock.call_count == 1
assert len(trades) == 4
assert len(trades) == MOCK_TRADE_COUNT
assert isinstance(trades, DataFrame)
assert "pair" in trades.columns
assert "open_date" in trades.columns

View File

@@ -2,13 +2,11 @@
import logging
from freqtrade.configuration.timerange import TimeRange
from freqtrade.data.converter import (convert_ohlcv_format,
convert_trades_format,
ohlcv_fill_up_missing_data,
ohlcv_to_dataframe, trades_dict_to_list,
trades_remove_duplicates, trim_dataframe)
from freqtrade.data.history import (get_timerange, load_data,
load_pair_history, validate_backtest_data)
from freqtrade.data.converter import (convert_ohlcv_format, convert_trades_format,
ohlcv_fill_up_missing_data, ohlcv_to_dataframe,
trades_dict_to_list, trades_remove_duplicates, trim_dataframe)
from freqtrade.data.history import (get_timerange, load_data, load_pair_history,
validate_backtest_data)
from tests.conftest import log_has
from tests.data.test_history import _backup_file, _clean_test_file

View File

@@ -132,7 +132,7 @@ def test_orderbook(mocker, default_conf, order_book_l2):
res = dp.orderbook('ETH/BTC', 5)
assert order_book_l2.call_count == 1
assert order_book_l2.call_args_list[0][0][0] == 'ETH/BTC'
assert order_book_l2.call_args_list[0][0][1] == 5
assert order_book_l2.call_args_list[0][0][1] >= 5
assert type(res) is dict
assert 'bids' in res

View File

@@ -15,20 +15,19 @@ from freqtrade.configuration import TimeRange
from freqtrade.constants import AVAILABLE_DATAHANDLERS
from freqtrade.data.converter import ohlcv_to_dataframe
from freqtrade.data.history.hdf5datahandler import HDF5DataHandler
from freqtrade.data.history.history_utils import (
_download_pair_history, _download_trades_history,
_load_cached_data_for_updating, convert_trades_to_ohlcv, get_timerange,
load_data, load_pair_history, refresh_backtest_ohlcv_data,
refresh_backtest_trades_data, refresh_data, validate_backtest_data)
from freqtrade.data.history.idatahandler import (IDataHandler, get_datahandler,
get_datahandlerclass)
from freqtrade.data.history.jsondatahandler import (JsonDataHandler,
JsonGzDataHandler)
from freqtrade.data.history.history_utils import (_download_pair_history, _download_trades_history,
_load_cached_data_for_updating,
convert_trades_to_ohlcv, get_timerange, load_data,
load_pair_history, refresh_backtest_ohlcv_data,
refresh_backtest_trades_data, refresh_data,
validate_backtest_data)
from freqtrade.data.history.idatahandler import IDataHandler, get_datahandler, get_datahandlerclass
from freqtrade.data.history.jsondatahandler import JsonDataHandler, JsonGzDataHandler
from freqtrade.exchange import timeframe_to_minutes
from freqtrade.misc import file_dump_json
from freqtrade.resolvers import StrategyResolver
from tests.conftest import (get_patched_exchange, log_has, log_has_re,
patch_exchange)
from tests.conftest import get_patched_exchange, log_has, log_has_re, patch_exchange
# Change this if modifying UNITTEST/BTC testdatafile
_BTC_UNITTEST_LENGTH = 13681

View File

@@ -10,14 +10,15 @@ import numpy as np
import pytest
from pandas import DataFrame, to_datetime
from freqtrade.exceptions import OperationalException
from freqtrade.data.converter import ohlcv_to_dataframe
from freqtrade.edge import Edge, PairInfo
from freqtrade.exceptions import OperationalException
from freqtrade.strategy.interface import SellType
from tests.conftest import get_patched_freqtradebot, log_has
from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe,
_get_frame_time_from_offset)
# Cases to be tested:
# 1) Open trade should be removed from the end
# 2) Two complete trades within dataframe (with sell hit for all)
@@ -498,3 +499,61 @@ def test_process_expectancy_remove_pumps(mocker, edge_conf, fee,):
assert final['TEST/BTC'].stoploss == -0.9
assert final['TEST/BTC'].nb_trades == len(trades_df) - 1
assert round(final['TEST/BTC'].winrate, 10) == 0.0
def test_process_expectancy_only_wins(mocker, edge_conf, fee,):
edge_conf['edge']['min_trade_number'] = 2
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
freqtrade.exchange.get_fee = fee
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
trades = [
{'pair': 'TEST/BTC',
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_date': np.datetime64('2018-10-03T00:05:00.000000000'),
'close_date': np.datetime64('2018-10-03T00:10:00.000000000'),
'open_index': 1,
'close_index': 1,
'trade_duration': '',
'open_rate': 15,
'close_rate': 17,
'exit_type': 'sell_signal'},
{'pair': 'TEST/BTC',
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_date': np.datetime64('2018-10-03T00:20:00.000000000'),
'close_date': np.datetime64('2018-10-03T00:25:00.000000000'),
'open_index': 4,
'close_index': 4,
'trade_duration': '',
'open_rate': 10,
'close_rate': 20,
'exit_type': 'sell_signal'},
{'pair': 'TEST/BTC',
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_date': np.datetime64('2018-10-03T00:30:00.000000000'),
'close_date': np.datetime64('2018-10-03T00:40:00.000000000'),
'open_index': 6,
'close_index': 7,
'trade_duration': '',
'open_rate': 26,
'close_rate': 134,
'exit_type': 'sell_signal'}
]
trades_df = DataFrame(trades)
trades_df = edge._fill_calculable_fields(trades_df)
final = edge._process_expectancy(trades_df)
assert 'TEST/BTC' in final
assert final['TEST/BTC'].stoploss == -0.9
assert final['TEST/BTC'].nb_trades == len(trades_df)
assert round(final['TEST/BTC'].winrate, 10) == 1.0
assert round(final['TEST/BTC'].risk_reward_ratio, 10) == float('inf')
assert round(final['TEST/BTC'].expectancy, 10) == float('inf')

View File

@@ -4,8 +4,7 @@ from unittest.mock import MagicMock
import ccxt
import pytest
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
OperationalException)
from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException
from tests.conftest import get_patched_exchange
from tests.exchange.test_exchange import ccxt_exceptionhandlers

View File

@@ -1,5 +1,3 @@
# pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement
# pragma pylint: disable=protected-access
import copy
import logging
from datetime import datetime, timezone
@@ -11,20 +9,18 @@ import ccxt
import pytest
from pandas import DataFrame
from freqtrade.exceptions import (DDosProtection, DependencyException,
InvalidOrderException, OperationalException,
TemporaryError)
from freqtrade.exchange import Binance, Exchange, Kraken
from freqtrade.exchange.common import API_RETRY_COUNT, calculate_backoff
from freqtrade.exchange.exchange import (market_is_active,
timeframe_to_minutes,
timeframe_to_msecs,
timeframe_to_next_date,
timeframe_to_prev_date,
from freqtrade.exceptions import (DDosProtection, DependencyException, InvalidOrderException,
OperationalException, TemporaryError)
from freqtrade.exchange import Binance, Bittrex, Exchange, Kraken
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_COUNT,
calculate_backoff)
from freqtrade.exchange.exchange import (market_is_active, timeframe_to_minutes, timeframe_to_msecs,
timeframe_to_next_date, timeframe_to_prev_date,
timeframe_to_seconds)
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
from tests.conftest import get_patched_exchange, log_has, log_has_re
# Make sure to always keep one exchange here which is NOT subclassed!!
EXCHANGES = ['bittrex', 'binance', 'kraken', 'ftx']
@@ -152,11 +148,19 @@ def test_exchange_resolver(default_conf, mocker, caplog):
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
exchange = ExchangeResolver.load_exchange('Bittrex', default_conf)
exchange = ExchangeResolver.load_exchange('huobi', default_conf)
assert isinstance(exchange, Exchange)
assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog)
caplog.clear()
exchange = ExchangeResolver.load_exchange('Bittrex', default_conf)
assert isinstance(exchange, Exchange)
assert isinstance(exchange, Bittrex)
assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.",
caplog)
caplog.clear()
exchange = ExchangeResolver.load_exchange('kraken', default_conf)
assert isinstance(exchange, Exchange)
assert isinstance(exchange, Kraken)
@@ -808,7 +812,7 @@ def test_dry_run_order(default_conf, mocker, side, exchange_name):
assert f'dry_run_{side}_' in order["id"]
assert order["side"] == side
assert order["type"] == "limit"
assert order["pair"] == "ETH/BTC"
assert order["symbol"] == "ETH/BTC"
@pytest.mark.parametrize("side", [
@@ -1442,6 +1446,27 @@ def test_refresh_latest_ohlcv_inv_result(default_conf, mocker, caplog):
assert log_has("Async code raised an exception: TypeError", caplog)
def test_get_next_limit_in_list():
limit_range = [5, 10, 20, 50, 100, 500, 1000]
assert Exchange.get_next_limit_in_list(1, limit_range) == 5
assert Exchange.get_next_limit_in_list(5, limit_range) == 5
assert Exchange.get_next_limit_in_list(6, limit_range) == 10
assert Exchange.get_next_limit_in_list(9, limit_range) == 10
assert Exchange.get_next_limit_in_list(10, limit_range) == 10
assert Exchange.get_next_limit_in_list(11, limit_range) == 20
assert Exchange.get_next_limit_in_list(19, limit_range) == 20
assert Exchange.get_next_limit_in_list(21, limit_range) == 50
assert Exchange.get_next_limit_in_list(51, limit_range) == 100
assert Exchange.get_next_limit_in_list(1000, limit_range) == 1000
# Going over the limit ...
assert Exchange.get_next_limit_in_list(1001, limit_range) == 1000
assert Exchange.get_next_limit_in_list(2000, limit_range) == 1000
assert Exchange.get_next_limit_in_list(21, None) == 21
assert Exchange.get_next_limit_in_list(100, None) == 100
assert Exchange.get_next_limit_in_list(1000, None) == 1000
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_fetch_l2_order_book(default_conf, mocker, order_book_l2, exchange_name):
default_conf['exchange']['name'] = exchange_name
@@ -1454,6 +1479,19 @@ def test_fetch_l2_order_book(default_conf, mocker, order_book_l2, exchange_name)
assert 'asks' in order_book
assert len(order_book['bids']) == 10
assert len(order_book['asks']) == 10
assert api_mock.fetch_l2_order_book.call_args_list[0][0][0] == 'ETH/BTC'
for val in [1, 5, 10, 12, 20, 50, 100]:
api_mock.fetch_l2_order_book.reset_mock()
order_book = exchange.fetch_l2_order_book(pair='ETH/BTC', limit=val)
assert api_mock.fetch_l2_order_book.call_args_list[0][0][0] == 'ETH/BTC'
# Not all exchanges support all limits for orderbook
if not exchange._ft_has['l2_limit_range'] or val in exchange._ft_has['l2_limit_range']:
assert api_mock.fetch_l2_order_book.call_args_list[0][0][1] == val
else:
next_limit = exchange.get_next_limit_in_list(val, exchange._ft_has['l2_limit_range'])
assert api_mock.fetch_l2_order_book.call_args_list[0][0][1] == next_limit
@pytest.mark.parametrize("exchange_name", EXCHANGES)
@@ -1766,7 +1804,7 @@ def test_cancel_order_dry_run(default_conf, mocker, exchange_name):
cancel_order = exchange.cancel_order(order_id=order['id'], pair='ETH/BTC')
assert order['id'] == cancel_order['id']
assert order['amount'] == cancel_order['amount']
assert order['pair'] == cancel_order['pair']
assert order['symbol'] == cancel_order['symbol']
assert cancel_order['status'] == 'canceled'
@@ -1903,12 +1941,14 @@ def test_fetch_order(default_conf, mocker, exchange_name):
# Ensure backoff is called
assert tm.call_args_list[0][0][0] == 1
assert tm.call_args_list[1][0][0] == 2
assert tm.call_args_list[2][0][0] == 5
assert tm.call_args_list[3][0][0] == 10
assert api_mock.fetch_order.call_count == 6
if API_FETCH_ORDER_RETRY_COUNT > 2:
assert tm.call_args_list[2][0][0] == 5
if API_FETCH_ORDER_RETRY_COUNT > 3:
assert tm.call_args_list[3][0][0] == 10
assert api_mock.fetch_order.call_count == API_FETCH_ORDER_RETRY_COUNT + 1
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
'fetch_order', 'fetch_order', retries=6,
'fetch_order', 'fetch_order', retries=API_FETCH_ORDER_RETRY_COUNT + 1,
order_id='_', pair='TKN/BTC')
@@ -1941,10 +1981,35 @@ def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
'fetch_stoploss_order', 'fetch_order',
retries=6,
retries=API_FETCH_ORDER_RETRY_COUNT + 1,
order_id='_', pair='TKN/BTC')
def test_fetch_order_or_stoploss_order(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, id='binance')
fetch_order_mock = MagicMock()
fetch_stoploss_order_mock = MagicMock()
mocker.patch.multiple('freqtrade.exchange.Exchange',
fetch_order=fetch_order_mock,
fetch_stoploss_order=fetch_stoploss_order_mock,
)
exchange.fetch_order_or_stoploss_order('1234', 'ETH/BTC', False)
assert fetch_order_mock.call_count == 1
assert fetch_order_mock.call_args_list[0][0][0] == '1234'
assert fetch_order_mock.call_args_list[0][0][1] == 'ETH/BTC'
assert fetch_stoploss_order_mock.call_count == 0
fetch_order_mock.reset_mock()
fetch_stoploss_order_mock.reset_mock()
exchange.fetch_order_or_stoploss_order('1234', 'ETH/BTC', True)
assert fetch_order_mock.call_count == 0
assert fetch_stoploss_order_mock.call_count == 1
assert fetch_stoploss_order_mock.call_args_list[0][0][0] == '1234'
assert fetch_stoploss_order_mock.call_args_list[0][0][1] == 'ETH/BTC'
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_name(default_conf, mocker, exchange_name):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)

View File

@@ -1,5 +1,3 @@
# pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement
# pragma pylint: disable=protected-access
from random import randint
from unittest.mock import MagicMock
@@ -7,10 +5,12 @@ import ccxt
import pytest
from freqtrade.exceptions import DependencyException, InvalidOrderException
from freqtrade.exchange.common import API_FETCH_ORDER_RETRY_COUNT
from tests.conftest import get_patched_exchange
from .test_exchange import ccxt_exceptionhandlers
STOPLOSS_ORDERTYPE = 'stop'
@@ -154,5 +154,5 @@ def test_fetch_stoploss_order(default_conf, mocker):
ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'ftx',
'fetch_stoploss_order', 'fetch_orders',
retries=6,
retries=API_FETCH_ORDER_RETRY_COUNT + 1,
order_id='_', pair='TKN/BTC')

View File

@@ -1,5 +1,3 @@
# pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement
# pragma pylint: disable=protected-access
from random import randint
from unittest.mock import MagicMock
@@ -10,6 +8,7 @@ from freqtrade.exceptions import DependencyException, InvalidOrderException
from tests.conftest import get_patched_exchange
from tests.exchange.test_exchange import ccxt_exceptionhandlers
STOPLOSS_ORDERTYPE = 'stop-loss'

View File

@@ -6,6 +6,7 @@ from pandas import DataFrame
from freqtrade.exchange import timeframe_to_minutes
from freqtrade.strategy.interface import SellType
tests_start_time = arrow.get(2018, 10, 3)
tests_timeframe = '1h'

View File

@@ -11,6 +11,7 @@ from tests.conftest import patch_exchange
from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe,
_get_frame_time_from_offset, tests_timeframe)
# Test 0: Sell with signal sell in candle 3
# Test with Stop-loss at 1%
tc0 = BTContainer(data=[

View File

@@ -10,11 +10,10 @@ import pytest
from arrow import Arrow
from freqtrade import constants
from freqtrade.commands.optimize_commands import (setup_optimize_configuration,
start_backtesting)
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_backtesting
from freqtrade.configuration import TimeRange
from freqtrade.data import history
from freqtrade.data.btanalysis import evaluate_result_multi
from freqtrade.data.btanalysis import BT_DATA_COLUMNS, evaluate_result_multi
from freqtrade.data.converter import clean_ohlcv_dataframe
from freqtrade.data.dataprovider import DataProvider
from freqtrade.data.history import get_timerange
@@ -26,6 +25,7 @@ from freqtrade.strategy.interface import SellType
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
patched_configuration_load_config_file)
ORDER_TYPES = [
{
'buy': 'limit',
@@ -694,7 +694,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
patch_exchange(mocker)
backtestmock = MagicMock()
backtestmock = MagicMock(return_value=pd.DataFrame(columns=BT_DATA_COLUMNS + ['profit_abs']))
mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['UNITTEST/BTC']))
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)

View File

@@ -1,11 +1,12 @@
# pragma pylint: disable=missing-docstring,W0212,C0103
import locale
import logging
import re
from copy import deepcopy
from datetime import datetime
from pathlib import Path
from copy import deepcopy
from typing import Dict, List
from unittest.mock import MagicMock, PropertyMock
from unittest.mock import MagicMock
import pandas as pd
import pytest
@@ -13,14 +14,12 @@ from arrow import Arrow
from filelock import Timeout
from freqtrade import constants
from freqtrade.commands.optimize_commands import (setup_optimize_configuration,
start_hyperopt)
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_hyperopt
from freqtrade.data.history import load_data
from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.optimize.default_hyperopt_loss import DefaultHyperOptLoss
from freqtrade.optimize.default_hyperopt_loss import ShortTradeDurHyperOptLoss
from freqtrade.optimize.hyperopt import Hyperopt
from freqtrade.resolvers.hyperopt_resolver import (HyperOptLossResolver,
HyperOptResolver)
from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver, HyperOptResolver
from freqtrade.state import RunMode
from freqtrade.strategy.interface import SellType
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
@@ -34,6 +33,7 @@ def hyperopt_conf(default_conf):
hyperconf = deepcopy(default_conf)
hyperconf.update({
'hyperopt': 'DefaultHyperOpt',
'hyperopt_loss': 'ShortTradeDurHyperOptLoss',
'hyperopt_path': str(Path(__file__).parent / 'hyperopts'),
'epochs': 1,
'timerange': None,
@@ -81,13 +81,14 @@ def create_results(mocker, hyperopt, testdatadir) -> List[Dict]:
mocker.patch.object(Path, "is_file", MagicMock(return_value=False))
stat_mock = MagicMock()
stat_mock.st_size = PropertyMock(return_value=1)
mocker.patch.object(Path, "stat", MagicMock(return_value=False))
stat_mock.st_size = 1
mocker.patch.object(Path, "stat", MagicMock(return_value=stat_mock))
mocker.patch.object(Path, "unlink", MagicMock(return_value=True))
mocker.patch('freqtrade.optimize.hyperopt.dump', return_value=None)
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
return [{'loss': 1, 'result': 'foo', 'params': {}}]
return [{'loss': 1, 'result': 'foo', 'params': {}, 'is_best': True}]
def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None:
@@ -229,13 +230,21 @@ def test_hyperoptresolver_noname(default_conf):
HyperOptResolver.load_hyperopt(default_conf)
def test_hyperoptlossresolver_noname(default_conf):
with pytest.raises(OperationalException,
match="No Hyperopt loss set. Please use `--hyperopt-loss` to specify "
"the Hyperopt-Loss class to use."):
HyperOptLossResolver.load_hyperoptloss(default_conf)
def test_hyperoptlossresolver(mocker, default_conf) -> None:
hl = DefaultHyperOptLoss
hl = ShortTradeDurHyperOptLoss
mocker.patch(
'freqtrade.resolvers.hyperopt_resolver.HyperOptLossResolver.load_object',
MagicMock(return_value=hl)
)
default_conf.update({'hyperopt_loss': 'SharpeHyperOptLossDaily'})
x = HyperOptLossResolver.load_hyperoptloss(default_conf)
assert hasattr(x, "hyperopt_loss_function")
@@ -278,6 +287,7 @@ def test_start(mocker, hyperopt_conf, caplog) -> None:
'hyperopt',
'--config', 'config.json',
'--hyperopt', 'DefaultHyperOpt',
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
'--epochs', '5'
]
pargs = get_args(args)
@@ -301,6 +311,7 @@ def test_start_no_data(mocker, hyperopt_conf) -> None:
'hyperopt',
'--config', 'config.json',
'--hyperopt', 'DefaultHyperOpt',
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
'--epochs', '5'
]
pargs = get_args(args)
@@ -318,6 +329,7 @@ def test_start_filelock(mocker, hyperopt_conf, caplog) -> None:
'hyperopt',
'--config', 'config.json',
'--hyperopt', 'DefaultHyperOpt',
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
'--epochs', '5'
]
pargs = get_args(args)
@@ -325,8 +337,8 @@ def test_start_filelock(mocker, hyperopt_conf, caplog) -> None:
assert log_has("Another running instance of freqtrade Hyperopt detected.", caplog)
def test_loss_calculation_prefer_correct_trade_count(default_conf, hyperopt_results) -> None:
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
def test_loss_calculation_prefer_correct_trade_count(hyperopt_conf, hyperopt_results) -> None:
hl = HyperOptLossResolver.load_hyperoptloss(hyperopt_conf)
correct = hl.hyperopt_loss_function(hyperopt_results, 600,
datetime(2019, 1, 1), datetime(2019, 5, 1))
over = hl.hyperopt_loss_function(hyperopt_results, 600 + 100,
@@ -337,11 +349,11 @@ def test_loss_calculation_prefer_correct_trade_count(default_conf, hyperopt_resu
assert under > correct
def test_loss_calculation_prefer_shorter_trades(default_conf, hyperopt_results) -> None:
def test_loss_calculation_prefer_shorter_trades(hyperopt_conf, hyperopt_results) -> None:
resultsb = hyperopt_results.copy()
resultsb.loc[1, 'trade_duration'] = 20
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
hl = HyperOptLossResolver.load_hyperoptloss(hyperopt_conf)
longer = hl.hyperopt_loss_function(hyperopt_results, 100,
datetime(2019, 1, 1), datetime(2019, 5, 1))
shorter = hl.hyperopt_loss_function(resultsb, 100,
@@ -349,13 +361,13 @@ def test_loss_calculation_prefer_shorter_trades(default_conf, hyperopt_results)
assert shorter < longer
def test_loss_calculation_has_limited_profit(default_conf, hyperopt_results) -> None:
def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) -> None:
results_over = hyperopt_results.copy()
results_over['profit_percent'] = hyperopt_results['profit_percent'] * 2
results_under = hyperopt_results.copy()
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
hl = HyperOptLossResolver.load_hyperoptloss(hyperopt_conf)
correct = hl.hyperopt_loss_function(hyperopt_results, 600,
datetime(2019, 1, 1), datetime(2019, 5, 1))
over = hl.hyperopt_loss_function(results_over, 600,
@@ -372,7 +384,7 @@ def test_sharpe_loss_prefers_higher_profits(default_conf, hyperopt_results) -> N
results_under = hyperopt_results.copy()
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
default_conf.update({'hyperopt_loss': 'SharpeHyperOptLoss'})
default_conf.update({'hyperopt_loss': 'SharpeHyperOptLossDaily'})
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
correct = hl.hyperopt_loss_function(hyperopt_results, len(hyperopt_results),
datetime(2019, 1, 1), datetime(2019, 5, 1))
@@ -497,6 +509,7 @@ def test_no_log_if_loss_does_not_improve(hyperopt, caplog) -> None:
def test_save_results_saves_epochs(mocker, hyperopt, testdatadir, caplog) -> None:
epochs = create_results(mocker, hyperopt, testdatadir)
mock_dump = mocker.patch('freqtrade.optimize.hyperopt.dump', return_value=None)
mock_dump_json = mocker.patch('freqtrade.optimize.hyperopt.file_dump_json', return_value=None)
results_file = testdatadir / 'optimize' / 'ut_results.pickle'
caplog.set_level(logging.DEBUG)
@@ -505,6 +518,7 @@ def test_save_results_saves_epochs(mocker, hyperopt, testdatadir, caplog) -> Non
hyperopt._save_results()
assert log_has(f"1 epoch saved to '{results_file}'.", caplog)
mock_dump.assert_called_once()
mock_dump_json.assert_called_once()
hyperopt.epochs = epochs + epochs
hyperopt._save_results()
@@ -521,6 +535,28 @@ def test_read_results_returns_epochs(mocker, hyperopt, testdatadir, caplog) -> N
mock_load.assert_called_once()
def test_load_previous_results(mocker, hyperopt, testdatadir, caplog) -> None:
epochs = create_results(mocker, hyperopt, testdatadir)
mock_load = mocker.patch('freqtrade.optimize.hyperopt.load', return_value=epochs)
mocker.patch.object(Path, 'is_file', MagicMock(return_value=True))
statmock = MagicMock()
statmock.st_size = 5
# mocker.patch.object(Path, 'stat', MagicMock(return_value=statmock))
results_file = testdatadir / 'optimize' / 'ut_results.pickle'
hyperopt_epochs = hyperopt.load_previous_results(results_file)
assert hyperopt_epochs == epochs
mock_load.assert_called_once()
del epochs[0]['is_best']
mock_load = mocker.patch('freqtrade.optimize.hyperopt.load', return_value=epochs)
with pytest.raises(OperationalException):
hyperopt.load_previous_results(results_file)
def test_roi_table_generation(hyperopt) -> None:
params = {
'roi_t1': 5,
@@ -536,6 +572,8 @@ def test_roi_table_generation(hyperopt) -> None:
def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
mocker.patch(
@@ -813,7 +851,7 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
'draws': 0,
'duration': 100.0,
'losses': 0,
'winsdrawslosses': '1/0/0',
'winsdrawslosses': ' 1 0 0',
'median_profit': 2.3117,
'profit': 2.3117,
'total_profit': 0.000233,
@@ -839,19 +877,10 @@ def test_clean_hyperopt(mocker, hyperopt_conf, caplog):
assert log_has(f"Removing `{h.data_pickle_file}`.", caplog)
def test_continue_hyperopt(mocker, hyperopt_conf, caplog):
patch_exchange(mocker)
hyperopt_conf.update({'hyperopt_continue': True})
mocker.patch("freqtrade.optimize.hyperopt.Path.is_file", MagicMock(return_value=True))
unlinkmock = mocker.patch("freqtrade.optimize.hyperopt.Path.unlink", MagicMock())
Hyperopt(hyperopt_conf)
assert unlinkmock.call_count == 0
assert log_has("Continuing on previous hyperopt results.", caplog)
def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
mocker.patch(
@@ -907,6 +936,7 @@ def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
mocker.patch(
@@ -954,6 +984,7 @@ def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
mocker.patch(
@@ -1000,6 +1031,7 @@ def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
mocker.patch(
@@ -1052,6 +1084,7 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non
def test_simplified_interface_all_failed(mocker, hyperopt_conf) -> None:
mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
mocker.patch(
@@ -1078,6 +1111,7 @@ def test_simplified_interface_all_failed(mocker, hyperopt_conf) -> None:
def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
mocker.patch(
@@ -1130,6 +1164,7 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
mocker.patch(
@@ -1188,6 +1223,7 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
])
def test_simplified_interface_failed(mocker, hyperopt_conf, method, space) -> None:
mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
mocker.patch(
@@ -1207,3 +1243,39 @@ def test_simplified_interface_failed(mocker, hyperopt_conf, method, space) -> No
with pytest.raises(OperationalException, match=f"The '{space}' space is included into *"):
hyperopt.start()
def test_print_epoch_details(capsys):
test_result = {
'params_details': {
'trailing': {
'trailing_stop': True,
'trailing_stop_positive': 0.02,
'trailing_stop_positive_offset': 0.04,
'trailing_only_offset_is_reached': True
},
'roi': {
0: 0.18,
90: 0.14,
225: 0.05,
430: 0},
},
'results_explanation': 'foo result',
'is_initial_point': False,
'total_profit': 0,
'current_epoch': 2, # This starts from 1 (in a human-friendly manner)
'is_best': True
}
Hyperopt.print_epoch_details(test_result, 5, False, no_header=True)
captured = capsys.readouterr()
assert '# Trailing stop:' in captured.out
# re.match(r"Pairs for .*", captured.out)
assert re.search(r'^\s+trailing_stop = True$', captured.out, re.MULTILINE)
assert re.search(r'^\s+trailing_stop_positive = 0.02$', captured.out, re.MULTILINE)
assert re.search(r'^\s+trailing_stop_positive_offset = 0.04$', captured.out, re.MULTILINE)
assert re.search(r'^\s+trailing_only_offset_is_reached = True$', captured.out, re.MULTILINE)
assert '# ROI table:' in captured.out
assert re.search(r'^\s+minimal_roi = \{$', captured.out, re.MULTILINE)
assert re.search(r'^\s+\"90\"\:\s0.14,\s*$', captured.out, re.MULTILINE)

View File

@@ -9,24 +9,20 @@ from arrow import Arrow
from freqtrade.configuration import TimeRange
from freqtrade.constants import LAST_BT_RESULT_FN
from freqtrade.data import history
from freqtrade.data.btanalysis import (get_latest_backtest_filename,
load_backtest_data)
from freqtrade.data.btanalysis import get_latest_backtest_filename, load_backtest_data
from freqtrade.edge import PairInfo
from freqtrade.optimize.optimize_reports import (generate_backtest_stats,
generate_daily_stats,
generate_edge_table,
generate_pair_metrics,
from freqtrade.optimize.optimize_reports import (generate_backtest_stats, generate_daily_stats,
generate_edge_table, generate_pair_metrics,
generate_sell_reason_stats,
generate_strategy_metrics,
store_backtest_stats,
text_table_bt_results,
text_table_sell_reason,
generate_strategy_metrics, store_backtest_stats,
text_table_bt_results, text_table_sell_reason,
text_table_strategy)
from freqtrade.resolvers.strategy_resolver import StrategyResolver
from freqtrade.strategy.interface import SellType
from tests.data.test_history import _backup_file, _clean_test_file
def test_text_table_bt_results(default_conf, mocker):
def test_text_table_bt_results():
results = pd.DataFrame(
{
@@ -57,32 +53,38 @@ def test_text_table_bt_results(default_conf, mocker):
def test_generate_backtest_stats(default_conf, testdatadir):
results = {'DefStrat': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC",
"UNITTEST/BTC", "UNITTEST/BTC"],
"profit_percent": [0.003312, 0.010801, 0.013803, 0.002780],
"profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
"open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
Arrow(2017, 11, 14, 21, 36, 00).datetime,
Arrow(2017, 11, 14, 22, 12, 00).datetime,
Arrow(2017, 11, 14, 22, 44, 00).datetime],
"close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime,
Arrow(2017, 11, 14, 22, 10, 00).datetime,
Arrow(2017, 11, 14, 22, 43, 00).datetime,
Arrow(2017, 11, 14, 22, 58, 00).datetime],
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"trade_duration": [123, 34, 31, 14],
"open_at_end": [False, False, False, True],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL]
})}
default_conf.update({'strategy': 'DefaultStrategy'})
StrategyResolver.load_strategy(default_conf)
results = {'DefStrat': {
'results': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC",
"UNITTEST/BTC", "UNITTEST/BTC"],
"profit_percent": [0.003312, 0.010801, 0.013803, 0.002780],
"profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
"open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
Arrow(2017, 11, 14, 21, 36, 00).datetime,
Arrow(2017, 11, 14, 22, 12, 00).datetime,
Arrow(2017, 11, 14, 22, 44, 00).datetime],
"close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime,
Arrow(2017, 11, 14, 22, 10, 00).datetime,
Arrow(2017, 11, 14, 22, 43, 00).datetime,
Arrow(2017, 11, 14, 22, 58, 00).datetime],
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"trade_duration": [123, 34, 31, 14],
"open_at_end": [False, False, False, True],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL]
}),
'config': default_conf}
}
timerange = TimeRange.parse_timerange('1510688220-1510700340')
min_date = Arrow.fromtimestamp(1510688220)
max_date = Arrow.fromtimestamp(1510700340)
btdata = history.load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange,
fill_up_missing=True)
stats = generate_backtest_stats(default_conf, btdata, results, min_date, max_date)
stats = generate_backtest_stats(btdata, results, min_date, max_date)
assert isinstance(stats, dict)
assert 'strategy' in stats
assert 'DefStrat' in stats['strategy']
@@ -90,29 +92,32 @@ def test_generate_backtest_stats(default_conf, testdatadir):
strat_stats = stats['strategy']['DefStrat']
assert strat_stats['backtest_start'] == min_date.datetime
assert strat_stats['backtest_end'] == max_date.datetime
assert strat_stats['total_trades'] == len(results['DefStrat'])
assert strat_stats['total_trades'] == len(results['DefStrat']['results'])
# Above sample had no loosing trade
assert strat_stats['max_drawdown'] == 0.0
results = {'DefStrat': pd.DataFrame(
{"pair": ["UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC"],
"profit_percent": [0.003312, 0.010801, -0.013803, 0.002780],
"profit_abs": [0.000003, 0.000011, -0.000014, 0.000003],
"open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
Arrow(2017, 11, 14, 21, 36, 00).datetime,
Arrow(2017, 11, 14, 22, 12, 00).datetime,
Arrow(2017, 11, 14, 22, 44, 00).datetime],
"close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime,
Arrow(2017, 11, 14, 22, 10, 00).datetime,
Arrow(2017, 11, 14, 22, 43, 00).datetime,
Arrow(2017, 11, 14, 22, 58, 00).datetime],
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
"close_rate": [0.002546, 0.003014, 0.0032903, 0.003217],
"trade_duration": [123, 34, 31, 14],
"open_at_end": [False, False, False, True],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL]
})}
results = {'DefStrat': {
'results': pd.DataFrame(
{"pair": ["UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC"],
"profit_percent": [0.003312, 0.010801, -0.013803, 0.002780],
"profit_abs": [0.000003, 0.000011, -0.000014, 0.000003],
"open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
Arrow(2017, 11, 14, 21, 36, 00).datetime,
Arrow(2017, 11, 14, 22, 12, 00).datetime,
Arrow(2017, 11, 14, 22, 44, 00).datetime],
"close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime,
Arrow(2017, 11, 14, 22, 10, 00).datetime,
Arrow(2017, 11, 14, 22, 43, 00).datetime,
Arrow(2017, 11, 14, 22, 58, 00).datetime],
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
"close_rate": [0.002546, 0.003014, 0.0032903, 0.003217],
"trade_duration": [123, 34, 31, 14],
"open_at_end": [False, False, False, True],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL]
}),
'config': default_conf}
}
assert strat_stats['max_drawdown'] == 0.0
assert strat_stats['drawdown_start'] == Arrow.fromtimestamp(0).datetime
@@ -165,7 +170,7 @@ def test_store_backtest_stats(testdatadir, mocker):
assert str(dump_mock.call_args_list[0][0][0]).startswith(str(testdatadir / 'testresult'))
def test_generate_pair_metrics(default_conf, mocker):
def test_generate_pair_metrics():
results = pd.DataFrame(
{
@@ -213,7 +218,7 @@ def test_generate_daily_stats(testdatadir):
assert res['losing_days'] == 0
def test_text_table_sell_reason(default_conf):
def test_text_table_sell_reason():
results = pd.DataFrame(
{
@@ -245,7 +250,7 @@ def test_text_table_sell_reason(default_conf):
stake_currency='BTC') == result_str
def test_generate_sell_reason_stats(default_conf):
def test_generate_sell_reason_stats():
results = pd.DataFrame(
{
@@ -280,9 +285,10 @@ def test_generate_sell_reason_stats(default_conf):
assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2)
def test_text_table_strategy(default_conf, mocker):
def test_text_table_strategy(default_conf):
default_conf['max_open_trades'] = 2
results = {}
results['TestStrategy1'] = pd.DataFrame(
results['TestStrategy1'] = {'results': pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
'profit_percent': [0.1, 0.2, 0.3],
@@ -293,8 +299,8 @@ def test_text_table_strategy(default_conf, mocker):
'losses': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
}
)
results['TestStrategy2'] = pd.DataFrame(
), 'config': default_conf}
results['TestStrategy2'] = {'results': pd.DataFrame(
{
'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'],
'profit_percent': [0.4, 0.2, 0.3],
@@ -305,7 +311,7 @@ def test_text_table_strategy(default_conf, mocker):
'losses': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
}
)
), 'config': default_conf}
result_str = (
'| Strategy | Buys | Avg Profit % | Cum Profit % | Tot'
@@ -318,14 +324,12 @@ def test_text_table_strategy(default_conf, mocker):
' 45.00 | 0:20:00 | 3 | 0 | 0 |'
)
strategy_results = generate_strategy_metrics(stake_currency='BTC',
max_open_trades=2,
all_results=results)
strategy_results = generate_strategy_metrics(all_results=results)
assert text_table_strategy(strategy_results, 'BTC') == result_str
def test_generate_edge_table(edge_conf, mocker):
def test_generate_edge_table():
results = {}
results['ETH/BTC'] = PairInfo(-0.01, 0.60, 2, 1, 3, 10, 60)

View File

@@ -13,8 +13,7 @@ from freqtrade.persistence import Trade
from freqtrade.rpc import RPC, RPCException
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
from freqtrade.state import State
from tests.conftest import (create_mock_trades, get_patched_freqtradebot,
patch_get_signal)
from tests.conftest import create_mock_trades, get_patched_freqtradebot, patch_get_signal
# Functions for recurrent object patching
@@ -313,7 +312,6 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog):
with pytest.raises(RPCException, match='invalid argument'):
rpc._rpc_delete('200')
create_mock_trades(fee)
trades = Trade.query.all()
trades[1].stoploss_order_id = '1234'
trades[2].stoploss_order_id = '1234'
@@ -717,11 +715,13 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
mocker.patch(
'freqtrade.exchange.Exchange.fetch_order',
side_effect=[{
'id': '1234',
'status': 'open',
'type': 'limit',
'side': 'buy',
'filled': filled_amount
}, {
'id': '1234',
'status': 'closed',
'type': 'limit',
'side': 'buy',
@@ -837,10 +837,10 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
assert counts["current"] == 1
def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order) -> None:
def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> None:
default_conf['forcebuy_enable'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
buy_mm = MagicMock(return_value={'id': limit_buy_order['id']})
buy_mm = MagicMock(return_value=limit_buy_order_open)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),

View File

@@ -2,7 +2,8 @@
Unit test file for rpc/api_server.py
"""
from datetime import datetime
from datetime import datetime, timedelta
from pathlib import Path
from unittest.mock import ANY, MagicMock, PropertyMock
import pytest
@@ -11,11 +12,11 @@ from requests.auth import _basic_auth_str
from freqtrade.__init__ import __version__
from freqtrade.loggers import setup_logging, setup_logging_pre
from freqtrade.persistence import Trade
from freqtrade.persistence import PairLock, Trade
from freqtrade.rpc.api_server import BASE_URI, ApiServer
from freqtrade.state import State
from tests.conftest import (create_mock_trades, get_patched_freqtradebot,
log_has, patch_get_signal)
from tests.conftest import create_mock_trades, get_patched_freqtradebot, log_has, patch_get_signal
_TEST_USER = "FreqTrader"
_TEST_PASS = "SuperSecurePassword1!"
@@ -327,6 +328,30 @@ def test_api_count(botclient, mocker, ticker, fee, markets):
assert rc.json["max"] == 1.0
def test_api_locks(botclient):
ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/locks")
assert_response(rc)
assert 'locks' in rc.json
assert rc.json['lock_count'] == 0
assert rc.json['lock_count'] == len(rc.json['locks'])
PairLock.lock_pair('ETH/BTC', datetime.utcnow() + timedelta(minutes=4), 'randreason')
PairLock.lock_pair('XRP/BTC', datetime.utcnow() + timedelta(minutes=20), 'deadbeef')
rc = client_get(client, f"{BASE_URI}/locks")
assert_response(rc)
assert rc.json['lock_count'] == 2
assert rc.json['lock_count'] == len(rc.json['locks'])
assert 'ETH/BTC' in (rc.json['locks'][0]['pair'], rc.json['locks'][1]['pair'])
assert 'randreason' in (rc.json['locks'][0]['reason'], rc.json['locks'][1]['reason'])
assert 'deadbeef' in (rc.json['locks'][0]['reason'], rc.json['locks'][1]['reason'])
def test_api_show_config(botclient, mocker):
ftbot, client = botclient
patch_get_signal(ftbot, (True, False))
@@ -811,3 +836,176 @@ def test_api_forcesell(botclient, mocker, ticker, fee, markets):
data='{"tradeid": "1"}')
assert_response(rc)
assert rc.json == {'result': 'Created sell order for trade 1.'}
def test_api_pair_candles(botclient, ohlcv_history):
ftbot, client = botclient
timeframe = '5m'
amount = 2
# No pair
rc = client_get(client,
f"{BASE_URI}/pair_candles?limit={amount}&timeframe={timeframe}")
assert_response(rc, 400)
# No timeframe
rc = client_get(client,
f"{BASE_URI}/pair_candles?pair=XRP%2FBTC")
assert_response(rc, 400)
rc = client_get(client,
f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}")
assert_response(rc)
assert 'columns' in rc.json
assert 'data_start_ts' in rc.json
assert 'data_start' in rc.json
assert 'data_stop' in rc.json
assert 'data_stop_ts' in rc.json
assert len(rc.json['data']) == 0
ohlcv_history['sma'] = ohlcv_history['close'].rolling(2).mean()
ohlcv_history['buy'] = 0
ohlcv_history.loc[1, 'buy'] = 1
ohlcv_history['sell'] = 0
ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history)
rc = client_get(client,
f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}")
assert_response(rc)
assert 'strategy' in rc.json
assert rc.json['strategy'] == 'DefaultStrategy'
assert 'columns' in rc.json
assert 'data_start_ts' in rc.json
assert 'data_start' in rc.json
assert 'data_stop' in rc.json
assert 'data_stop_ts' in rc.json
assert rc.json['data_start'] == '2017-11-26 08:50:00+00:00'
assert rc.json['data_start_ts'] == 1511686200000
assert rc.json['data_stop'] == '2017-11-26 08:55:00+00:00'
assert rc.json['data_stop_ts'] == 1511686500000
assert isinstance(rc.json['columns'], list)
assert rc.json['columns'] == ['date', 'open', 'high',
'low', 'close', 'volume', 'sma', 'buy', 'sell',
'__date_ts', '_buy_signal_open', '_sell_signal_open']
assert 'pair' in rc.json
assert rc.json['pair'] == 'XRP/BTC'
assert 'data' in rc.json
assert len(rc.json['data']) == amount
assert (rc.json['data'] ==
[['2017-11-26 08:50:00', 8.794e-05, 8.948e-05, 8.794e-05, 8.88e-05, 0.0877869,
None, 0, 0, 1511686200000, None, None],
['2017-11-26 08:55:00', 8.88e-05, 8.942e-05, 8.88e-05,
8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0, 1511686500000, 8.88e-05, None]
])
def test_api_pair_history(botclient, ohlcv_history):
ftbot, client = botclient
timeframe = '5m'
# No pair
rc = client_get(client,
f"{BASE_URI}/pair_history?timeframe={timeframe}"
"&timerange=20180111-20180112&strategy=DefaultStrategy")
assert_response(rc, 400)
# No Timeframe
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC"
"&timerange=20180111-20180112&strategy=DefaultStrategy")
assert_response(rc, 400)
# No timerange
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
"&strategy=DefaultStrategy")
assert_response(rc, 400)
# No strategy
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
"&timerange=20180111-20180112")
assert_response(rc, 400)
# Working
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
"&timerange=20180111-20180112&strategy=DefaultStrategy")
assert_response(rc, 200)
assert rc.json['length'] == 289
assert len(rc.json['data']) == rc.json['length']
assert 'columns' in rc.json
assert 'data' in rc.json
assert rc.json['pair'] == 'UNITTEST/BTC'
assert rc.json['strategy'] == 'DefaultStrategy'
assert rc.json['data_start'] == '2018-01-11 00:00:00+00:00'
assert rc.json['data_start_ts'] == 1515628800000
assert rc.json['data_stop'] == '2018-01-12 00:00:00+00:00'
assert rc.json['data_stop_ts'] == 1515715200000
def test_api_plot_config(botclient):
ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/plot_config")
assert_response(rc)
assert rc.json == {}
ftbot.strategy.plot_config = {'main_plot': {'sma': {}},
'subplots': {'RSI': {'rsi': {'color': 'red'}}}}
rc = client_get(client, f"{BASE_URI}/plot_config")
assert_response(rc)
assert rc.json == ftbot.strategy.plot_config
assert isinstance(rc.json['main_plot'], dict)
def test_api_strategies(botclient):
ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/strategies")
assert_response(rc)
assert rc.json == {'strategies': ['DefaultStrategy', 'TestStrategyLegacy']}
def test_api_strategy(botclient):
ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/strategy/DefaultStrategy")
assert_response(rc)
assert rc.json['strategy'] == 'DefaultStrategy'
data = (Path(__file__).parents[1] / "strategy/strats/default_strategy.py").read_text()
assert rc.json['code'] == data
rc = client_get(client, f"{BASE_URI}/strategy/NoStrat")
assert_response(rc, 404)
def test_list_available_pairs(botclient):
ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/available_pairs")
assert_response(rc)
assert rc.json['length'] == 12
assert isinstance(rc.json['pairs'], list)
rc = client_get(client, f"{BASE_URI}/available_pairs?timeframe=5m")
assert_response(rc)
assert rc.json['length'] == 12
rc = client_get(client, f"{BASE_URI}/available_pairs?stake_currency=ETH")
assert_response(rc)
assert rc.json['length'] == 1
assert rc.json['pairs'] == ['XRP/ETH']
assert len(rc.json['pair_interval']) == 2
rc = client_get(client, f"{BASE_URI}/available_pairs?stake_currency=ETH&timeframe=5m")
assert_response(rc)
assert rc.json['length'] == 1
assert rc.json['pairs'] == ['XRP/ETH']
assert len(rc.json['pair_interval']) == 1

View File

@@ -1,10 +1,10 @@
# pragma pylint: disable=missing-docstring, C0103
import time
import logging
import time
from unittest.mock import MagicMock
from freqtrade.rpc import RPCMessageType, RPCManager
from tests.conftest import log_has, get_patched_freqtradebot
from freqtrade.rpc import RPCManager, RPCMessageType
from tests.conftest import get_patched_freqtradebot, log_has
def test__init__(mocker, default_conf) -> None:
@@ -124,10 +124,10 @@ def test_send_msg_webhook_CustomMessagetype(mocker, default_conf, caplog) -> Non
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
assert 'webhook' in [mod.name for mod in rpc_manager.registered_modules]
rpc_manager.send_msg({'type': RPCMessageType.CUSTOM_NOTIFICATION,
rpc_manager.send_msg({'type': RPCMessageType.STARTUP_NOTIFICATION,
'status': 'TestMessage'})
assert log_has(
"Message type RPCMessageType.CUSTOM_NOTIFICATION not implemented by handler webhook.",
"Message type 'startup' not implemented by handler webhook.",
caplog)

View File

@@ -18,14 +18,13 @@ from freqtrade.constants import CANCEL_REASON
from freqtrade.edge import PairInfo
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.loggers import setup_logging
from freqtrade.persistence import Trade
from freqtrade.persistence import PairLock, Trade
from freqtrade.rpc import RPCMessageType
from freqtrade.rpc.telegram import Telegram, authorized_only
from freqtrade.state import State
from freqtrade.strategy.interface import SellType
from tests.conftest import (create_mock_trades, get_patched_freqtradebot,
log_has, patch_exchange, patch_get_signal,
patch_whitelist)
from tests.conftest import (create_mock_trades, get_patched_freqtradebot, log_has, patch_exchange,
patch_get_signal, patch_whitelist)
class DummyCls(Telegram):
@@ -76,15 +75,15 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], "
"['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], "
"['delete'], ['performance'], ['daily'], ['count'], ['reload_config', "
"'reload_conf'], ['show_config', 'show_conf'], ['stopbuy'], "
"['delete'], ['performance'], ['daily'], ['count'], ['locks'], "
"['reload_config', 'reload_conf'], ['show_config', 'show_conf'], ['stopbuy'], "
"['whitelist'], ['blacklist'], ['logs'], ['edge'], ['help'], ['version'], "
"['stats']]")
assert log_has(message_str, caplog)
def test_cleanup(default_conf, mocker) -> None:
def test_cleanup(default_conf, mocker, ) -> None:
updater_mock = MagicMock()
updater_mock.stop = MagicMock()
mocker.patch('freqtrade.rpc.telegram.Updater', updater_mock)
@@ -94,13 +93,9 @@ def test_cleanup(default_conf, mocker) -> None:
assert telegram._updater.stop.call_count == 1
def test_authorized_only(default_conf, mocker, caplog) -> None:
def test_authorized_only(default_conf, mocker, caplog, update) -> None:
patch_exchange(mocker)
chat = Chat(0, 0)
update = Update(randint(1, 100))
update.message = Message(randint(1, 100), 0, datetime.utcnow(), chat)
default_conf['telegram']['enabled'] = False
bot = FreqtradeBot(default_conf)
patch_get_signal(bot, (True, False))
@@ -116,7 +111,7 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
patch_exchange(mocker)
chat = Chat(0xdeadbeef, 0)
update = Update(randint(1, 100))
update.message = Message(randint(1, 100), 0, datetime.utcnow(), chat)
update.message = Message(randint(1, 100), datetime.utcnow(), chat)
default_conf['telegram']['enabled'] = False
bot = FreqtradeBot(default_conf)
@@ -129,12 +124,9 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
assert not log_has('Exception occurred within Telegram module', caplog)
def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None:
patch_exchange(mocker)
update = Update(randint(1, 100))
update.message = Message(randint(1, 100), 0, datetime.utcnow(), Chat(0, 0))
default_conf['telegram']['enabled'] = False
bot = FreqtradeBot(default_conf)
@@ -148,7 +140,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
assert log_has('Exception occurred within Telegram module', caplog)
def test_telegram_status(default_conf, update, mocker, fee, ticker,) -> None:
def test_telegram_status(default_conf, update, mocker) -> None:
update.message.chat.id = "123"
default_conf['telegram']['enabled'] = False
default_conf['telegram']['chat_id'] = "123"
@@ -254,7 +246,6 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': 'mocked_order_id'}),
get_fee=fee,
)
msg_mock = MagicMock()
@@ -1008,7 +999,6 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': 'mocked_order_id'}),
get_fee=fee,
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
@@ -1035,6 +1025,43 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
assert msg in msg_mock.call_args_list[0][0][0]
def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None:
msg_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
_send_msg=msg_mock
)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
get_fee=fee,
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False))
telegram = Telegram(freqtradebot)
freqtradebot.state = State.STOPPED
telegram._locks(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert 'not running' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
freqtradebot.state = State.RUNNING
PairLock.lock_pair('ETH/BTC', arrow.utcnow().shift(minutes=4).datetime, 'randreason')
PairLock.lock_pair('XRP/BTC', arrow.utcnow().shift(minutes=20).datetime, 'deadbeef')
telegram._locks(update=update, context=MagicMock())
assert 'Pair' in msg_mock.call_args_list[0][0][0]
assert 'Until' in msg_mock.call_args_list[0][0][0]
assert 'Reason\n' in msg_mock.call_args_list[0][0][0]
assert 'ETH/BTC' in msg_mock.call_args_list[0][0][0]
assert 'XRP/BTC' in msg_mock.call_args_list[0][0][0]
assert 'deadbeef' in msg_mock.call_args_list[0][0][0]
assert 'randreason' in msg_mock.call_args_list[0][0][0]
def test_whitelist_static(default_conf, update, mocker) -> None:
msg_mock = MagicMock()
mocker.patch.multiple(
@@ -1142,9 +1169,9 @@ def test_telegram_logs(default_conf, update, mocker) -> None:
context = MagicMock()
context.args = []
telegram._logs(update=update, context=context)
# Called at least 3 times. Exact times will change with unrelated changes to setup messages
# Called at least 2 times. Exact times will change with unrelated changes to setup messages
# Therefore we don't test for this explicitly.
assert msg_mock.call_count > 3
assert msg_mock.call_count >= 2
def test_edge_disabled(default_conf, update, mocker) -> None:
@@ -1302,16 +1329,14 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
def test_send_msg_buy_notification(default_conf, mocker) -> None:
def test_send_msg_buy_notification(default_conf, mocker, caplog) -> 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({
msg = {
'type': RPCMessageType.BUY_NOTIFICATION,
'exchange': 'Bittrex',
'pair': 'ETH/BTC',
@@ -1324,7 +1349,10 @@ def test_send_msg_buy_notification(default_conf, mocker) -> None:
'current_rate': 1.099e-05,
'amount': 1333.3333333333335,
'open_date': arrow.utcnow().shift(hours=-1)
})
}
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot)
telegram.send_msg(msg)
assert msg_mock.call_args[0][0] \
== '\N{LARGE BLUE CIRCLE} *Bittrex:* Buying ETH/BTC\n' \
'*Amount:* `1333.33333333`\n' \
@@ -1332,6 +1360,21 @@ def test_send_msg_buy_notification(default_conf, mocker) -> None:
'*Current Rate:* `0.00001099`\n' \
'*Total:* `(0.001000 BTC, 12.345 USD)`'
freqtradebot.config['telegram']['notification_settings'] = {'buy': 'off'}
caplog.clear()
msg_mock.reset_mock()
telegram.send_msg(msg)
msg_mock.call_count == 0
log_has("Notification 'buy' not sent.", caplog)
freqtradebot.config['telegram']['notification_settings'] = {'buy': 'silent'}
caplog.clear()
msg_mock.reset_mock()
telegram.send_msg(msg)
msg_mock.call_count == 1
msg_mock.call_args_list[0][1]['disable_notification'] is True
def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None:
msg_mock = MagicMock()
@@ -1488,7 +1531,7 @@ def test_warning_notification(default_conf, mocker) -> None:
assert msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Warning:* `message`'
def test_custom_notification(default_conf, mocker) -> None:
def test_startup_notification(default_conf, mocker) -> None:
msg_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
@@ -1498,7 +1541,7 @@ def test_custom_notification(default_conf, mocker) -> None:
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot)
telegram.send_msg({
'type': RPCMessageType.CUSTOM_NOTIFICATION,
'type': RPCMessageType.STARTUP_NOTIFICATION,
'status': '*Custom:* `Hello World`'
})
assert msg_mock.call_args[0][0] == '*Custom:* `Hello World`'

View File

@@ -150,7 +150,7 @@ def test_send_msg(default_conf, mocker):
default_conf["webhook"]["webhooksellcancel"]["value3"].format(**msg))
for msgtype in [RPCMessageType.STATUS_NOTIFICATION,
RPCMessageType.WARNING_NOTIFICATION,
RPCMessageType.CUSTOM_NOTIFICATION]:
RPCMessageType.STARTUP_NOTIFICATION]:
# Test notification
msg = {
'type': msgtype,
@@ -174,7 +174,7 @@ def test_exception_send_msg(default_conf, mocker, caplog):
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf))
webhook.send_msg({'type': RPCMessageType.BUY_NOTIFICATION})
assert log_has(f"Message type {RPCMessageType.BUY_NOTIFICATION} not configured for webhooks",
assert log_has(f"Message type '{RPCMessageType.BUY_NOTIFICATION}' not configured for webhooks",
caplog)
default_conf["webhook"] = get_webhook_dict()

View File

@@ -1,12 +1,13 @@
# --- Do not remove these libs ---
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
# --------------------------------
# Add your lib to import here
import talib.abstract as ta
from pandas import DataFrame
from freqtrade.strategy.interface import IStrategy
# --------------------------------
# This class is a sample. Feel free to customize it.
class TestStrategyLegacy(IStrategy):

View File

@@ -1,5 +1,4 @@
# pragma pylint: disable=missing-docstring, C0103
import logging
from datetime import datetime, timedelta, timezone
from unittest.mock import MagicMock
@@ -12,13 +11,14 @@ from freqtrade.configuration import TimeRange
from freqtrade.data.dataprovider import DataProvider
from freqtrade.data.history import load_data
from freqtrade.exceptions import StrategyError
from freqtrade.persistence import Trade
from freqtrade.persistence import PairLock, Trade
from freqtrade.resolvers import StrategyResolver
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from tests.conftest import log_has, log_has_re
from .strats.default_strategy import DefaultStrategy
# Avoid to reinit the same object again and again
_STRATEGY = DefaultStrategy(config={})
_STRATEGY.dp = DataProvider({}, None, None)
@@ -359,11 +359,12 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) ->
assert log_has('Skipping TA Analysis for already analyzed candle', caplog)
@pytest.mark.usefixtures("init_persistence")
def test_is_pair_locked(default_conf):
default_conf.update({'strategy': 'DefaultStrategy'})
strategy = StrategyResolver.load_strategy(default_conf)
# dict should be empty
assert not strategy._pair_locked_until
# No lock should be present
assert len(PairLock.query.all()) == 0
pair = 'ETH/BTC'
assert not strategy.is_pair_locked(pair)
@@ -371,11 +372,6 @@ def test_is_pair_locked(default_conf):
# ETH/BTC locked for 4 minutes
assert strategy.is_pair_locked(pair)
# Test lock does not change
lock = strategy._pair_locked_until[pair]
strategy.lock_pair(pair, arrow.utcnow().shift(minutes=2).datetime)
assert lock == strategy._pair_locked_until[pair]
# XRP/BTC should not be locked now
pair = 'XRP/BTC'
assert not strategy.is_pair_locked(pair)
@@ -392,7 +388,7 @@ def test_is_pair_locked(default_conf):
# Lock until 14:30
lock_time = datetime(2020, 5, 1, 14, 30, 0, tzinfo=timezone.utc)
strategy.lock_pair(pair, lock_time)
# Lock is in the past ...
assert not strategy.is_pair_locked(pair)
# latest candle is from 14:20, lock goes to 14:30
assert strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-10))

View File

@@ -18,13 +18,15 @@ def test_search_strategy():
s, _ = StrategyResolver._search_object(
directory=default_location,
object_name='DefaultStrategy'
object_name='DefaultStrategy',
add_source=True,
)
assert issubclass(s, IStrategy)
s, _ = StrategyResolver._search_object(
directory=default_location,
object_name='NotFoundStrategy'
object_name='NotFoundStrategy',
add_source=True,
)
assert s is None
@@ -53,6 +55,9 @@ def test_load_strategy(default_conf, result):
'strategy_path': str(Path(__file__).parents[2] / 'freqtrade/templates')
})
strategy = StrategyResolver.load_strategy(default_conf)
assert isinstance(strategy.__source__, str)
assert 'class SampleStrategy' in strategy.__source__
assert isinstance(strategy.__file__, str)
assert 'rsi' in strategy.advise_indicators(result, {'pair': 'ETH/BTC'})

View File

@@ -1,5 +1,5 @@
import pandas as pd
import numpy as np
import pandas as pd
from freqtrade.strategy import merge_informative_pair, timeframe_to_minutes

View File

@@ -11,20 +11,18 @@ import pytest
from jsonschema import ValidationError
from freqtrade.commands import Arguments
from freqtrade.configuration import (Configuration, check_exchange,
remove_credentials,
from freqtrade.configuration import (Configuration, check_exchange, remove_credentials,
validate_config_consistency)
from freqtrade.configuration.config_validation import validate_config_schema
from freqtrade.configuration.deprecated_settings import (
check_conflicting_settings, process_deprecated_setting,
process_temporary_deprecated_settings)
from freqtrade.configuration.deprecated_settings import (check_conflicting_settings,
process_deprecated_setting,
process_temporary_deprecated_settings)
from freqtrade.configuration.load_config import load_config_file, log_config_error_range
from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL
from freqtrade.exceptions import OperationalException
from freqtrade.loggers import _set_loggers, setup_logging, setup_logging_pre
from freqtrade.state import RunMode
from tests.conftest import (log_has, log_has_re,
patched_configuration_load_config_file)
from tests.conftest import log_has, log_has_re, patched_configuration_load_config_file
@pytest.fixture(scope="function")

View File

@@ -4,8 +4,7 @@ from unittest.mock import MagicMock
import pytest
from freqtrade.configuration.directory_operations import (copy_sample_files,
create_datadir,
from freqtrade.configuration.directory_operations import (copy_sample_files, create_datadir,
create_userdata_dir)
from freqtrade.exceptions import OperationalException
from tests.conftest import log_has, log_has_re

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,8 @@
import freqtrade.vendor.qtpylib.indicators as qtpylib
import numpy as np
import pandas as pd
import freqtrade.vendor.qtpylib.indicators as qtpylib
def test_crossed_numpy_types():
"""

View File

@@ -1,11 +1,11 @@
# pragma pylint: disable=missing-docstring
from copy import deepcopy
from pathlib import Path
from unittest.mock import MagicMock, PropertyMock
import pytest
from pathlib import Path
from freqtrade.commands import Arguments
from freqtrade.exceptions import FreqtradeException, OperationalException
from freqtrade.freqtradebot import FreqtradeBot
@@ -65,7 +65,7 @@ def test_main_fatal_exception(mocker, default_conf, caplog) -> None:
mocker.patch('freqtrade.worker.Worker._worker', MagicMock(side_effect=Exception))
patched_configuration_load_config_file(mocker, default_conf)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock())
args = ['trade', '-c', 'config.json.example']
@@ -83,7 +83,7 @@ def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None:
patched_configuration_load_config_file(mocker, default_conf)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
mocker.patch('freqtrade.wallets.Wallets.update', MagicMock())
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock())
args = ['trade', '-c', 'config.json.example']
@@ -104,7 +104,7 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None:
patched_configuration_load_config_file(mocker, default_conf)
mocker.patch('freqtrade.wallets.Wallets.update', MagicMock())
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock())
args = ['trade', '-c', 'config.json.example']
@@ -155,7 +155,7 @@ def test_main_reload_config(mocker, default_conf, caplog) -> None:
reconfigure_mock = mocker.patch('freqtrade.worker.Worker._reconfigure', MagicMock())
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock())
args = Arguments(['trade', '-c', 'config.json.example']).get_parsed_arg()
worker = Worker(args=args, config=default_conf)
@@ -178,7 +178,7 @@ def test_reconfigure(mocker, default_conf) -> None:
mocker.patch('freqtrade.wallets.Wallets.update', MagicMock())
patched_configuration_load_config_file(mocker, default_conf)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
mocker.patch('freqtrade.freqtradebot.init_db', MagicMock())
args = Arguments(['trade', '-c', 'config.json.example']).get_parsed_arg()
worker = Worker(args=args, config=default_conf)

View File

@@ -7,9 +7,8 @@ from unittest.mock import MagicMock
import pytest
from freqtrade.data.converter import ohlcv_to_dataframe
from freqtrade.misc import (datesarray_to_datetimearray, file_dump_json,
file_load_json, format_ms_time, pair_to_filename,
plural, render_template,
from freqtrade.misc import (datesarray_to_datetimearray, file_dump_json, file_load_json,
format_ms_time, pair_to_filename, plural, render_template,
render_template_with_fallback, safe_value_fallback,
safe_value_fallback2, shorten_date)

View File

@@ -1,5 +1,6 @@
# pragma pylint: disable=missing-docstring, C0103
import logging
from datetime import datetime, timedelta, timezone
from unittest.mock import MagicMock
import arrow
@@ -7,14 +8,14 @@ import pytest
from sqlalchemy import create_engine
from freqtrade import constants
from freqtrade.exceptions import OperationalException
from freqtrade.persistence import Trade, clean_dry_run_db, init
from tests.conftest import log_has, create_mock_trades
from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.persistence import Order, PairLock, Trade, clean_dry_run_db, init_db
from tests.conftest import create_mock_trades, log_has, log_has_re
def test_init_create_session(default_conf):
# Check if init create a session
init(default_conf['db_url'], default_conf['dry_run'])
init_db(default_conf['db_url'], default_conf['dry_run'])
assert hasattr(Trade, 'session')
assert 'scoped_session' in type(Trade.session).__name__
@@ -22,9 +23,9 @@ def test_init_create_session(default_conf):
def test_init_custom_db_url(default_conf, mocker):
# Update path to a value other than default, but still in-memory
default_conf.update({'db_url': 'sqlite:///tmp/freqtrade2_test.sqlite'})
create_engine_mock = mocker.patch('freqtrade.persistence.create_engine', MagicMock())
create_engine_mock = mocker.patch('freqtrade.persistence.models.create_engine', MagicMock())
init(default_conf['db_url'], default_conf['dry_run'])
init_db(default_conf['db_url'], default_conf['dry_run'])
assert create_engine_mock.call_count == 1
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tmp/freqtrade2_test.sqlite'
@@ -33,16 +34,16 @@ def test_init_invalid_db_url(default_conf):
# Update path to a value other than default, but still in-memory
default_conf.update({'db_url': 'unknown:///some.url'})
with pytest.raises(OperationalException, match=r'.*no valid database URL*'):
init(default_conf['db_url'], default_conf['dry_run'])
init_db(default_conf['db_url'], default_conf['dry_run'])
def test_init_prod_db(default_conf, mocker):
default_conf.update({'dry_run': False})
default_conf.update({'db_url': constants.DEFAULT_DB_PROD_URL})
create_engine_mock = mocker.patch('freqtrade.persistence.create_engine', MagicMock())
create_engine_mock = mocker.patch('freqtrade.persistence.models.create_engine', MagicMock())
init(default_conf['db_url'], default_conf['dry_run'])
init_db(default_conf['db_url'], default_conf['dry_run'])
assert create_engine_mock.call_count == 1
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tradesv3.sqlite'
@@ -51,9 +52,9 @@ def test_init_dryrun_db(default_conf, mocker):
default_conf.update({'dry_run': True})
default_conf.update({'db_url': constants.DEFAULT_DB_DRYRUN_URL})
create_engine_mock = mocker.patch('freqtrade.persistence.create_engine', MagicMock())
create_engine_mock = mocker.patch('freqtrade.persistence.models.create_engine', MagicMock())
init(default_conf['db_url'], default_conf['dry_run'])
init_db(default_conf['db_url'], default_conf['dry_run'])
assert create_engine_mock.call_count == 1
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tradesv3.dryrun.sqlite'
@@ -93,6 +94,8 @@ def test_update_with_bittrex(limit_buy_order, limit_sell_order, fee, caplog):
stake_amount=0.001,
open_rate=0.01,
amount=5,
is_open=True,
open_date=arrow.utcnow().datetime,
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange='bittrex',
@@ -107,9 +110,9 @@ def test_update_with_bittrex(limit_buy_order, limit_sell_order, fee, caplog):
assert trade.open_rate == 0.00001099
assert trade.close_profit is None
assert trade.close_date is None
assert log_has("LIMIT_BUY has been fulfilled for Trade(id=2, "
"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001099, open_since=closed).",
caplog)
assert log_has_re(r"LIMIT_BUY has been fulfilled for Trade\(id=2, "
r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001099, open_since=.*\).",
caplog)
caplog.clear()
trade.open_order_id = 'something'
@@ -118,9 +121,9 @@ def test_update_with_bittrex(limit_buy_order, limit_sell_order, fee, caplog):
assert trade.close_rate == 0.00001173
assert trade.close_profit == 0.06201058
assert trade.close_date is not None
assert log_has("LIMIT_SELL has been fulfilled for Trade(id=2, "
"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001099, open_since=closed).",
caplog)
assert log_has_re(r"LIMIT_SELL has been fulfilled for Trade\(id=2, "
r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001099, open_since=.*\).",
caplog)
@pytest.mark.usefixtures("init_persistence")
@@ -131,8 +134,10 @@ def test_update_market_order(market_buy_order, market_sell_order, fee, caplog):
stake_amount=0.001,
amount=5,
open_rate=0.01,
is_open=True,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_date=arrow.utcnow().datetime,
exchange='bittrex',
)
@@ -142,20 +147,21 @@ def test_update_market_order(market_buy_order, market_sell_order, fee, caplog):
assert trade.open_rate == 0.00004099
assert trade.close_profit is None
assert trade.close_date is None
assert log_has("MARKET_BUY has been fulfilled for Trade(id=1, "
"pair=ETH/BTC, amount=91.99181073, open_rate=0.00004099, open_since=closed).",
caplog)
assert log_has_re(r"MARKET_BUY has been fulfilled for Trade\(id=1, "
r"pair=ETH/BTC, amount=91.99181073, open_rate=0.00004099, open_since=.*\).",
caplog)
caplog.clear()
trade.is_open = True
trade.open_order_id = 'something'
trade.update(market_sell_order)
assert trade.open_order_id is None
assert trade.close_rate == 0.00004173
assert trade.close_profit == 0.01297561
assert trade.close_date is not None
assert log_has("MARKET_SELL has been fulfilled for Trade(id=1, "
"pair=ETH/BTC, amount=91.99181073, open_rate=0.00004099, open_since=closed).",
caplog)
assert log_has_re(r"MARKET_SELL has been fulfilled for Trade\(id=1, "
r"pair=ETH/BTC, amount=91.99181073, open_rate=0.00004099, open_since=.*\).",
caplog)
@pytest.mark.usefixtures("init_persistence")
@@ -184,6 +190,36 @@ def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee):
assert trade.calc_profit_ratio() == 0.06201058
@pytest.mark.usefixtures("init_persistence")
def test_trade_close(limit_buy_order, limit_sell_order, fee):
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
open_rate=0.01,
amount=5,
is_open=True,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_date=arrow.Arrow(2020, 2, 1, 15, 5, 1).datetime,
exchange='bittrex',
)
assert trade.close_profit is None
assert trade.close_date is None
assert trade.is_open is True
trade.close(0.02)
assert trade.is_open is False
assert trade.close_profit == 0.99002494
assert trade.close_date is not None
new_date = arrow.Arrow(2020, 2, 2, 15, 6, 1).datetime,
assert trade.close_date != new_date
# Close should NOT update close_date if the trade has been closed already
assert trade.is_open is False
trade.close_date = new_date
trade.close(0.02)
assert trade.close_date == new_date
@pytest.mark.usefixtures("init_persistence")
def test_calc_close_trade_price_exception(limit_buy_order, fee):
trade = Trade(
@@ -421,9 +457,9 @@ def test_migrate_old(mocker, default_conf, fee):
PRIMARY KEY (id),
CHECK (is_open IN (0, 1))
);"""
insert_table_old = """INSERT INTO trades (exchange, pair, is_open, fee,
insert_table_old = """INSERT INTO trades (exchange, pair, is_open, open_order_id, fee,
open_rate, stake_amount, amount, open_date)
VALUES ('BITTREX', 'BTC_ETC', 1, {fee},
VALUES ('BITTREX', 'BTC_ETC', 1, '123123', {fee},
0.00258580, {stake}, {amount},
'2017-11-28 12:44:24.000000')
""".format(fee=fee.return_value,
@@ -440,14 +476,14 @@ def test_migrate_old(mocker, default_conf, fee):
amount=amount
)
engine = create_engine('sqlite://')
mocker.patch('freqtrade.persistence.create_engine', lambda *args, **kwargs: engine)
mocker.patch('freqtrade.persistence.models.create_engine', lambda *args, **kwargs: engine)
# Create table using the old format
engine.execute(create_table_old)
engine.execute(insert_table_old)
engine.execute(insert_table_old2)
# Run init to test migration
init(default_conf['db_url'], default_conf['dry_run'])
init_db(default_conf['db_url'], default_conf['dry_run'])
assert len(Trade.query.filter(Trade.id == 1).all()) == 1
trade = Trade.query.filter(Trade.id == 1).first()
@@ -481,6 +517,12 @@ def test_migrate_old(mocker, default_conf, fee):
assert pytest.approx(trade.close_profit_abs) == trade.calc_profit()
assert trade.sell_order_status is None
# Should've created one order
assert len(Order.query.all()) == 1
order = Order.query.first()
assert order.order_id == '123123'
assert order.ft_order_side == 'buy'
def test_migrate_new(mocker, default_conf, fee, caplog):
"""
@@ -509,22 +551,25 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
sell_reason VARCHAR,
strategy VARCHAR,
ticker_interval INTEGER,
stoploss_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,
stop_loss, initial_stop_loss, max_rate, ticker_interval)
stop_loss, initial_stop_loss, max_rate, ticker_interval,
open_order_id, stoploss_order_id)
VALUES ('binance', 'ETC/BTC', 1, {fee},
0.00258580, {stake}, {amount},
'2019-11-28 12:44:24.000000',
0.0, 0.0, 0.0, '5m')
0.0, 0.0, 0.0, '5m',
'buy_order', 'stop_order_id222')
""".format(fee=fee.return_value,
stake=default_conf.get("stake_amount"),
amount=amount
)
engine = create_engine('sqlite://')
mocker.patch('freqtrade.persistence.create_engine', lambda *args, **kwargs: engine)
mocker.patch('freqtrade.persistence.models.create_engine', lambda *args, **kwargs: engine)
# Create table using the old format
engine.execute(create_table_old)
@@ -537,7 +582,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
engine.execute("create table trades_bak1 as select * from trades")
# Run init to test migration
init(default_conf['db_url'], default_conf['dry_run'])
init_db(default_conf['db_url'], default_conf['dry_run'])
assert len(Trade.query.filter(Trade.id == 1).all()) == 1
trade = Trade.query.filter(Trade.id == 1).first()
@@ -558,14 +603,23 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
assert trade.sell_reason is None
assert trade.strategy is None
assert trade.timeframe == '5m'
assert trade.stoploss_order_id is None
assert trade.stoploss_order_id == 'stop_order_id222'
assert trade.stoploss_last_update is None
assert log_has("trying trades_bak1", caplog)
assert log_has("trying trades_bak2", caplog)
assert log_has("Running database migration - backup available as trades_bak2", caplog)
assert log_has("Running database migration for trades - backup: trades_bak2", caplog)
assert trade.open_trade_price == trade._calc_open_trade_price()
assert trade.close_profit_abs is None
assert log_has("Moving open orders to Orders table.", caplog)
orders = Order.query.all()
assert len(orders) == 2
assert orders[0].order_id == 'buy_order'
assert orders[0].ft_order_side == 'buy'
assert orders[1].order_id == 'stop_order_id222'
assert orders[1].ft_order_side == 'stoploss'
def test_migrate_mid_state(mocker, default_conf, fee, caplog):
"""
@@ -601,14 +655,14 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog):
amount=amount
)
engine = create_engine('sqlite://')
mocker.patch('freqtrade.persistence.create_engine', lambda *args, **kwargs: engine)
mocker.patch('freqtrade.persistence.models.create_engine', lambda *args, **kwargs: engine)
# Create table using the old format
engine.execute(create_table_old)
engine.execute(insert_table_old)
# Run init to test migration
init(default_conf['db_url'], default_conf['dry_run'])
init_db(default_conf['db_url'], default_conf['dry_run'])
assert len(Trade.query.filter(Trade.id == 1).all()) == 1
trade = Trade.query.filter(Trade.id == 1).first()
@@ -626,7 +680,7 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog):
assert trade.initial_stop_loss == 0.0
assert trade.open_trade_price == trade._calc_open_trade_price()
assert log_has("trying trades_bak0", caplog)
assert log_has("Running database migration - backup available as trades_bak0", caplog)
assert log_has("Running database migration for trades - backup: trades_bak0", caplog)
def test_adjust_stop_loss(fee):
@@ -713,10 +767,10 @@ def test_adjust_min_max_rates(fee):
@pytest.mark.usefixtures("init_persistence")
def test_get_open(default_conf, fee):
def test_get_open(fee):
create_mock_trades(fee)
assert len(Trade.get_open_trades()) == 2
assert len(Trade.get_open_trades()) == 4
@pytest.mark.usefixtures("init_persistence")
@@ -851,7 +905,7 @@ def test_to_json(default_conf, fee):
def test_stoploss_reinitialization(default_conf, fee):
init(default_conf['db_url'])
init_db(default_conf['db_url'])
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
@@ -986,7 +1040,7 @@ def test_total_open_trades_stakes(fee):
assert res == 0
create_mock_trades(fee)
res = Trade.total_open_trades_stakes()
assert res == 0.002
assert res == 0.004
@pytest.mark.usefixtures("init_persistence")
@@ -1012,3 +1066,142 @@ def test_get_best_pair(fee):
assert len(res) == 2
assert res[0] == 'XRP/BTC'
assert res[1] == 0.01
@pytest.mark.usefixtures("init_persistence")
def test_update_order_from_ccxt():
# Most basic order return (only has orderid)
o = Order.parse_from_ccxt_object({'id': '1234'}, 'ETH/BTC', 'buy')
assert isinstance(o, Order)
assert o.ft_pair == 'ETH/BTC'
assert o.ft_order_side == 'buy'
assert o.order_id == '1234'
assert o.ft_is_open
ccxt_order = {
'id': '1234',
'side': 'buy',
'symbol': 'ETH/BTC',
'type': 'limit',
'price': 1234.5,
'amount': 20.0,
'filled': 9,
'remaining': 11,
'status': 'open',
'timestamp': 1599394315123
}
o = Order.parse_from_ccxt_object(ccxt_order, 'ETH/BTC', 'buy')
assert isinstance(o, Order)
assert o.ft_pair == 'ETH/BTC'
assert o.ft_order_side == 'buy'
assert o.order_id == '1234'
assert o.order_type == 'limit'
assert o.price == 1234.5
assert o.filled == 9
assert o.remaining == 11
assert o.order_date is not None
assert o.ft_is_open
assert o.order_filled_date is None
# Order has been closed
ccxt_order.update({'filled': 20.0, 'remaining': 0.0, 'status': 'closed'})
o.update_from_ccxt_object(ccxt_order)
assert o.filled == 20.0
assert o.remaining == 0.0
assert not o.ft_is_open
assert o.order_filled_date is not None
ccxt_order.update({'id': 'somethingelse'})
with pytest.raises(DependencyException, match=r"Order-id's don't match"):
o.update_from_ccxt_object(ccxt_order)
@pytest.mark.usefixtures("init_persistence")
def test_select_order(fee):
create_mock_trades(fee)
trades = Trade.get_trades().all()
# Open buy order, no sell order
order = trades[0].select_order('buy', True)
assert order is None
order = trades[0].select_order('buy', False)
assert order is not None
order = trades[0].select_order('sell', None)
assert order is None
# closed buy order, and open sell order
order = trades[1].select_order('buy', True)
assert order is None
order = trades[1].select_order('buy', False)
assert order is not None
order = trades[1].select_order('buy', None)
assert order is not None
order = trades[1].select_order('sell', True)
assert order is None
order = trades[1].select_order('sell', False)
assert order is not None
# Has open buy order
order = trades[3].select_order('buy', True)
assert order is not None
order = trades[3].select_order('buy', False)
assert order is None
# Open sell order
order = trades[4].select_order('buy', True)
assert order is None
order = trades[4].select_order('buy', False)
assert order is not None
order = trades[4].select_order('sell', True)
assert order is not None
assert order.ft_order_side == 'stoploss'
order = trades[4].select_order('sell', False)
assert order is None
@pytest.mark.usefixtures("init_persistence")
def test_PairLock(default_conf):
# No lock should be present
assert len(PairLock.query.all()) == 0
pair = 'ETH/BTC'
assert not PairLock.is_pair_locked(pair)
PairLock.lock_pair(pair, arrow.utcnow().shift(minutes=4).datetime)
# ETH/BTC locked for 4 minutes
assert PairLock.is_pair_locked(pair)
# XRP/BTC should not be locked now
pair = 'XRP/BTC'
assert not PairLock.is_pair_locked(pair)
# Unlocking a pair that's not locked should not raise an error
PairLock.unlock_pair(pair)
PairLock.lock_pair(pair, arrow.utcnow().shift(minutes=4).datetime)
assert PairLock.is_pair_locked(pair)
# Get both locks from above
locks = PairLock.get_pair_locks(None)
assert len(locks) == 2
# Unlock original pair
pair = 'ETH/BTC'
PairLock.unlock_pair(pair)
assert not PairLock.is_pair_locked(pair)
pair = 'BTC/USDT'
# Lock until 14:30
lock_time = datetime(2020, 5, 1, 14, 30, 0, tzinfo=timezone.utc)
PairLock.lock_pair(pair, lock_time)
assert not PairLock.is_pair_locked(pair)
assert PairLock.is_pair_locked(pair, lock_time + timedelta(minutes=-10))
assert PairLock.is_pair_locked(pair, lock_time + timedelta(minutes=-50))
# Should not be locked after time expired
assert not PairLock.is_pair_locked(pair, lock_time + timedelta(minutes=10))
locks = PairLock.get_pair_locks(pair, lock_time + timedelta(minutes=-2))
assert len(locks) == 1
assert 'PairLock' in str(locks[0])

View File

@@ -13,13 +13,10 @@ from freqtrade.configuration import TimeRange
from freqtrade.data import history
from freqtrade.data.btanalysis import create_cum_profit, load_backtest_data
from freqtrade.exceptions import OperationalException
from freqtrade.plot.plotting import (add_indicators, add_profit,
create_plotconfig,
generate_candlestick_graph,
generate_plot_filename,
generate_profit_graph, init_plotscript,
load_and_plot_trades, plot_profit,
plot_trades, store_plot_file)
from freqtrade.plot.plotting import (add_indicators, add_profit, create_plotconfig,
generate_candlestick_graph, generate_plot_filename,
generate_profit_graph, init_plotscript, load_and_plot_trades,
plot_profit, plot_trades, store_plot_file)
from freqtrade.resolvers import StrategyResolver
from tests.conftest import get_args, log_has, log_has_re, patch_exchange

View File

@@ -1,5 +1,5 @@
import talib.abstract as ta
import pandas as pd
import talib.abstract as ta
def test_talib_bollingerbands_near_zero_values():

View File

@@ -19,12 +19,17 @@ def test_sync_wallet_at_boot(mocker, default_conf):
"used": 0.0,
"total": 0.260739
},
"USDT": {
"free": 20,
"used": 20,
"total": 40
},
})
)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert len(freqtrade.wallets._wallets) == 2
assert len(freqtrade.wallets._wallets) == 3
assert freqtrade.wallets._wallets['BNT'].free == 1.0
assert freqtrade.wallets._wallets['BNT'].used == 2.0
assert freqtrade.wallets._wallets['BNT'].total == 3.0
@@ -32,6 +37,7 @@ def test_sync_wallet_at_boot(mocker, default_conf):
assert freqtrade.wallets._wallets['GAS'].used == 0.0
assert freqtrade.wallets._wallets['GAS'].total == 0.260739
assert freqtrade.wallets.get_free('BNT') == 1.0
assert 'USDT' in freqtrade.wallets._wallets
assert freqtrade.wallets._last_wallet_refresh > 0
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@@ -51,6 +57,7 @@ def test_sync_wallet_at_boot(mocker, default_conf):
freqtrade.wallets.update()
# USDT is missing from the 2nd result - so should not be in this either.
assert len(freqtrade.wallets._wallets) == 2
assert freqtrade.wallets._wallets['BNT'].free == 1.2
assert freqtrade.wallets._wallets['BNT'].used == 1.9