Merge branch 'develop' into pr/imxuwang/3799
This commit is contained in:
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
279
tests/conftest_trades.py
Normal 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
|
@@ -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
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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')
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -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')
|
||||
|
@@ -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'
|
||||
|
||||
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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=[
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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),
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
||||
|
||||
|
@@ -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`'
|
||||
|
@@ -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()
|
||||
|
@@ -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):
|
||||
|
@@ -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))
|
||||
|
@@ -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'})
|
||||
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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")
|
||||
|
@@ -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
@@ -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():
|
||||
"""
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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])
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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():
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user