Merge remote-tracking branch 'origin/develop' into feat/add-pytorch-model-support
This commit is contained in:
@@ -14,7 +14,8 @@ from freqtrade.commands import (start_backtesting_show, start_convert_data, star
|
||||
start_hyperopt_show, start_install_ui, start_list_data,
|
||||
start_list_exchanges, start_list_markets, start_list_strategies,
|
||||
start_list_timeframes, start_new_strategy, start_show_trades,
|
||||
start_test_pairlist, start_trading, start_webserver)
|
||||
start_strategy_update, start_test_pairlist, start_trading,
|
||||
start_webserver)
|
||||
from freqtrade.commands.db_commands import start_convert_db
|
||||
from freqtrade.commands.deploy_commands import (clean_ui_subdir, download_and_install_ui,
|
||||
get_ui_download_url, read_ui_version)
|
||||
@@ -1546,3 +1547,37 @@ def test_start_convert_db(mocker, fee, tmpdir, caplog):
|
||||
start_convert_db(pargs)
|
||||
|
||||
assert db_target_file.is_file()
|
||||
|
||||
|
||||
def test_start_strategy_updater(mocker, tmpdir):
|
||||
sc_mock = mocker.patch('freqtrade.commands.strategy_utils_commands.start_conversion')
|
||||
teststrats = Path(__file__).parent.parent / 'strategy/strats'
|
||||
args = [
|
||||
"strategy-updater",
|
||||
"--userdir",
|
||||
str(tmpdir),
|
||||
"--strategy-path",
|
||||
str(teststrats),
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_strategy_update(pargs)
|
||||
# Number of strategies in the test directory
|
||||
assert sc_mock.call_count == 11
|
||||
|
||||
sc_mock.reset_mock()
|
||||
args = [
|
||||
"strategy-updater",
|
||||
"--userdir",
|
||||
str(tmpdir),
|
||||
"--strategy-path",
|
||||
str(teststrats),
|
||||
"--strategy-list",
|
||||
"StrategyTestV3",
|
||||
"StrategyTestV2"
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_strategy_update(pargs)
|
||||
# Number of strategies in the test directory
|
||||
assert sc_mock.call_count == 2
|
||||
|
@@ -299,7 +299,7 @@ def create_mock_trades(fee, is_short: Optional[bool] = False, use_db: bool = Tru
|
||||
"""
|
||||
def add_trade(trade):
|
||||
if use_db:
|
||||
Trade.query.session.add(trade)
|
||||
Trade.session.add(trade)
|
||||
else:
|
||||
LocalTrade.add_bt_trade(trade)
|
||||
is_short1 = is_short if is_short is not None else True
|
||||
@@ -332,11 +332,11 @@ def create_mock_trades_with_leverage(fee, use_db: bool = True):
|
||||
Create some fake trades ...
|
||||
"""
|
||||
if use_db:
|
||||
Trade.query.session.rollback()
|
||||
Trade.session.rollback()
|
||||
|
||||
def add_trade(trade):
|
||||
if use_db:
|
||||
Trade.query.session.add(trade)
|
||||
Trade.session.add(trade)
|
||||
else:
|
||||
LocalTrade.add_bt_trade(trade)
|
||||
|
||||
@@ -366,7 +366,7 @@ def create_mock_trades_with_leverage(fee, use_db: bool = True):
|
||||
add_trade(trade)
|
||||
|
||||
if use_db:
|
||||
Trade.query.session.flush()
|
||||
Trade.session.flush()
|
||||
|
||||
|
||||
def create_mock_trades_usdt(fee, is_short: Optional[bool] = False, use_db: bool = True):
|
||||
@@ -375,7 +375,7 @@ def create_mock_trades_usdt(fee, is_short: Optional[bool] = False, use_db: bool
|
||||
"""
|
||||
def add_trade(trade):
|
||||
if use_db:
|
||||
Trade.query.session.add(trade)
|
||||
Trade.session.add(trade)
|
||||
else:
|
||||
LocalTrade.add_bt_trade(trade)
|
||||
|
||||
|
@@ -98,7 +98,7 @@ def test_load_backtest_data_new_format(testdatadir):
|
||||
assert bt_data.equals(bt_data3)
|
||||
|
||||
with pytest.raises(ValueError, match=r"File .* does not exist\."):
|
||||
load_backtest_data(str("filename") + "nofile")
|
||||
load_backtest_data("filename" + "nofile")
|
||||
|
||||
with pytest.raises(ValueError, match=r"Unknown dataformat."):
|
||||
load_backtest_data(testdatadir / "backtest_results" / LAST_BT_RESULT_FN)
|
||||
|
@@ -252,7 +252,7 @@ def test_datahandler__check_empty_df(testdatadir, caplog):
|
||||
assert log_has_re(expected_text, caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('datahandler', ['feather', 'parquet'])
|
||||
@pytest.mark.parametrize('datahandler', ['parquet'])
|
||||
def test_datahandler_trades_not_supported(datahandler, testdatadir, ):
|
||||
dh = get_datahandler(testdatadir, datahandler)
|
||||
with pytest.raises(NotImplementedError):
|
||||
@@ -496,6 +496,58 @@ def test_hdf5datahandler_ohlcv_purge(mocker, testdatadir):
|
||||
assert unlinkmock.call_count == 2
|
||||
|
||||
|
||||
def test_featherdatahandler_trades_load(testdatadir):
|
||||
dh = get_datahandler(testdatadir, 'feather')
|
||||
trades = dh.trades_load('XRP/ETH')
|
||||
assert isinstance(trades, list)
|
||||
assert trades[0][0] == 1570752011620
|
||||
assert trades[-1][-1] == 0.1986231
|
||||
|
||||
trades1 = dh.trades_load('UNITTEST/NONEXIST')
|
||||
assert trades1 == []
|
||||
|
||||
|
||||
def test_featherdatahandler_trades_store(testdatadir, tmpdir):
|
||||
tmpdir1 = Path(tmpdir)
|
||||
dh = get_datahandler(testdatadir, 'feather')
|
||||
trades = dh.trades_load('XRP/ETH')
|
||||
|
||||
dh1 = get_datahandler(tmpdir1, 'feather')
|
||||
dh1.trades_store('XRP/NEW', trades)
|
||||
file = tmpdir1 / 'XRP_NEW-trades.feather'
|
||||
assert file.is_file()
|
||||
# Load trades back
|
||||
trades_new = dh1.trades_load('XRP/NEW')
|
||||
|
||||
assert len(trades_new) == len(trades)
|
||||
assert trades[0][0] == trades_new[0][0]
|
||||
assert trades[0][1] == trades_new[0][1]
|
||||
# assert trades[0][2] == trades_new[0][2] # This is nan - so comparison does not make sense
|
||||
assert trades[0][3] == trades_new[0][3]
|
||||
assert trades[0][4] == trades_new[0][4]
|
||||
assert trades[0][5] == trades_new[0][5]
|
||||
assert trades[0][6] == trades_new[0][6]
|
||||
assert trades[-1][0] == trades_new[-1][0]
|
||||
assert trades[-1][1] == trades_new[-1][1]
|
||||
# assert trades[-1][2] == trades_new[-1][2] # This is nan - so comparison does not make sense
|
||||
assert trades[-1][3] == trades_new[-1][3]
|
||||
assert trades[-1][4] == trades_new[-1][4]
|
||||
assert trades[-1][5] == trades_new[-1][5]
|
||||
assert trades[-1][6] == trades_new[-1][6]
|
||||
|
||||
|
||||
def test_featherdatahandler_trades_purge(mocker, testdatadir):
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
|
||||
unlinkmock = mocker.patch.object(Path, "unlink", MagicMock())
|
||||
dh = get_datahandler(testdatadir, 'feather')
|
||||
assert not dh.trades_purge('UNITTEST/NONEXIST')
|
||||
assert unlinkmock.call_count == 0
|
||||
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
||||
assert dh.trades_purge('UNITTEST/NONEXIST')
|
||||
assert unlinkmock.call_count == 1
|
||||
|
||||
|
||||
def test_gethandlerclass():
|
||||
cl = get_datahandlerclass('json')
|
||||
assert cl == JsonDataHandler
|
||||
|
@@ -409,7 +409,7 @@ def test_init_with_refresh(default_conf, mocker) -> None:
|
||||
|
||||
|
||||
def test_file_dump_json_tofile(testdatadir) -> None:
|
||||
file = testdatadir / 'test_{id}.json'.format(id=str(uuid.uuid4()))
|
||||
file = testdatadir / f'test_{uuid.uuid4()}.json'
|
||||
data = {'bar': 'foo'}
|
||||
|
||||
# check the file we will create does not exist
|
||||
|
@@ -11,6 +11,19 @@ from tests.conftest import EXMS, get_mock_coro, get_patched_exchange, log_has_re
|
||||
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
|
||||
|
||||
@pytest.mark.parametrize('side,type,time_in_force,expected', [
|
||||
('buy', 'limit', 'gtc', {'timeInForce': 'GTC'}),
|
||||
('buy', 'limit', 'IOC', {'timeInForce': 'IOC'}),
|
||||
('buy', 'market', 'IOC', {}),
|
||||
('buy', 'limit', 'PO', {'timeInForce': 'PO'}),
|
||||
('sell', 'limit', 'PO', {'timeInForce': 'PO'}),
|
||||
('sell', 'market', 'PO', {}),
|
||||
])
|
||||
def test__get_params_binance(default_conf, mocker, side, type, time_in_force, expected):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='binance')
|
||||
assert exchange._get_params(side, type, 1, False, time_in_force) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('trademode', [TradingMode.FUTURES, TradingMode.SPOT])
|
||||
@pytest.mark.parametrize('limitratio,expected,side', [
|
||||
(None, 220 * 0.99, "sell"),
|
||||
@@ -35,11 +48,11 @@ def test_create_stoploss_order_binance(default_conf, mocker, limitratio, expecte
|
||||
default_conf['margin_mode'] = MarginMode.ISOLATED
|
||||
default_conf['trading_mode'] = trademode
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
with pytest.raises(InvalidOrderException):
|
||||
order = exchange.create_stoploss(
|
||||
pair='ETH/BTC',
|
||||
amount=1,
|
||||
@@ -114,11 +127,11 @@ def test_create_stoploss_order_dry_run_binance(default_conf, mocker):
|
||||
order_type = 'stop_loss_limit'
|
||||
default_conf['dry_run'] = True
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
with pytest.raises(InvalidOrderException):
|
||||
order = exchange.create_stoploss(
|
||||
pair='ETH/BTC',
|
||||
amount=1,
|
||||
@@ -542,7 +555,6 @@ def test__set_leverage_binance(mocker, default_conf):
|
||||
"set_leverage",
|
||||
pair="XRP/USDT",
|
||||
leverage=5.0,
|
||||
trading_mode=TradingMode.FUTURES
|
||||
)
|
||||
|
||||
|
||||
|
@@ -37,7 +37,7 @@ EXCHANGES = {
|
||||
'stake_currency': 'USDT',
|
||||
'use_ci_proxy': True,
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'timeframe': '1h',
|
||||
'futures': True,
|
||||
'futures_pair': 'BTC/USDT:USDT',
|
||||
'hasQuoteVolumeFutures': True,
|
||||
@@ -66,7 +66,7 @@ EXCHANGES = {
|
||||
'pair': 'BTC/USDT',
|
||||
'stake_currency': 'USDT',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'timeframe': '1h',
|
||||
'futures': False,
|
||||
'sample_order': [{
|
||||
"symbol": "SOLUSDT",
|
||||
@@ -91,7 +91,7 @@ EXCHANGES = {
|
||||
'pair': 'BTC/USDT',
|
||||
'stake_currency': 'USDT',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'timeframe': '1h',
|
||||
'leverage_tiers_public': False,
|
||||
'leverage_in_spot_market': True,
|
||||
},
|
||||
@@ -99,7 +99,7 @@ EXCHANGES = {
|
||||
'pair': 'XRP/USDT',
|
||||
'stake_currency': 'USDT',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'timeframe': '1h',
|
||||
'leverage_tiers_public': False,
|
||||
'leverage_in_spot_market': True,
|
||||
'sample_order': [
|
||||
@@ -141,7 +141,7 @@ EXCHANGES = {
|
||||
'pair': 'BTC/USDT',
|
||||
'stake_currency': 'USDT',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'timeframe': '1h',
|
||||
'futures': True,
|
||||
'futures_pair': 'BTC/USDT:USDT',
|
||||
'hasQuoteVolumeFutures': True,
|
||||
@@ -215,7 +215,7 @@ EXCHANGES = {
|
||||
'pair': 'BTC/USDT',
|
||||
'stake_currency': 'USDT',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'timeframe': '1h',
|
||||
'futures': True,
|
||||
'futures_pair': 'BTC/USDT:USDT',
|
||||
'hasQuoteVolumeFutures': False,
|
||||
@@ -226,7 +226,7 @@ EXCHANGES = {
|
||||
'pair': 'BTC/USDT',
|
||||
'stake_currency': 'USDT',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'timeframe': '1h',
|
||||
'futures_pair': 'BTC/USDT:USDT',
|
||||
'futures': True,
|
||||
'leverage_tiers_public': True,
|
||||
@@ -253,14 +253,14 @@ EXCHANGES = {
|
||||
'pair': 'ETH/BTC',
|
||||
'stake_currency': 'BTC',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'timeframe': '1h',
|
||||
'futures': False,
|
||||
},
|
||||
'bitvavo': {
|
||||
'pair': 'BTC/EUR',
|
||||
'stake_currency': 'EUR',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'timeframe': '1h',
|
||||
'leverage_tiers_public': False,
|
||||
'leverage_in_spot_market': False,
|
||||
},
|
||||
|
@@ -8,12 +8,13 @@ from unittest.mock import MagicMock, Mock, PropertyMock, patch
|
||||
import arrow
|
||||
import ccxt
|
||||
import pytest
|
||||
from ccxt import DECIMAL_PLACES, ROUND, ROUND_UP, TICK_SIZE, TRUNCATE
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||
from freqtrade.exceptions import (DDosProtection, DependencyException, ExchangeError,
|
||||
InvalidOrderException, OperationalException, PricingError,
|
||||
TemporaryError)
|
||||
InsufficientFundsError, InvalidOrderException,
|
||||
OperationalException, PricingError, TemporaryError)
|
||||
from freqtrade.exchange import (Binance, Bittrex, Exchange, Kraken, amount_to_precision,
|
||||
date_minus_candles, market_is_active, price_to_precision,
|
||||
timeframe_to_minutes, timeframe_to_msecs, timeframe_to_next_date,
|
||||
@@ -113,18 +114,21 @@ async def async_ccxt_exception(mocker, default_conf, api_mock, fun, mock_ccxt_fu
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
await getattr(exchange, fun)(**kwargs)
|
||||
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
|
||||
exchange.close()
|
||||
|
||||
with pytest.raises(TemporaryError):
|
||||
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError("DeadBeef"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
await getattr(exchange, fun)(**kwargs)
|
||||
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
|
||||
exchange.close()
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
await getattr(exchange, fun)(**kwargs)
|
||||
assert api_mock.__dict__[mock_ccxt_fun].call_count == 1
|
||||
exchange.close()
|
||||
|
||||
|
||||
def test_init(default_conf, mocker, caplog):
|
||||
@@ -312,35 +316,54 @@ def test_amount_to_precision(amount, precision_mode, precision, expected,):
|
||||
assert amount_to_precision(amount, precision, precision_mode) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("price,precision_mode,precision,expected", [
|
||||
(2.34559, 2, 4, 2.3456),
|
||||
(2.34559, 2, 5, 2.34559),
|
||||
(2.34559, 2, 3, 2.346),
|
||||
(2.9999, 2, 3, 3.000),
|
||||
(2.9909, 2, 3, 2.991),
|
||||
# Tests for Tick_size
|
||||
(2.34559, 4, 0.0001, 2.3456),
|
||||
(2.34559, 4, 0.00001, 2.34559),
|
||||
(2.34559, 4, 0.001, 2.346),
|
||||
(2.9999, 4, 0.001, 3.000),
|
||||
(2.9909, 4, 0.001, 2.991),
|
||||
(2.9909, 4, 0.005, 2.995),
|
||||
(2.9973, 4, 0.005, 3.0),
|
||||
(2.9977, 4, 0.005, 3.0),
|
||||
(234.43, 4, 0.5, 234.5),
|
||||
(234.53, 4, 0.5, 235.0),
|
||||
(0.891534, 4, 0.0001, 0.8916),
|
||||
(64968.89, 4, 0.01, 64968.89),
|
||||
(0.000000003483, 4, 1e-12, 0.000000003483),
|
||||
|
||||
@pytest.mark.parametrize("price,precision_mode,precision,expected,rounding_mode", [
|
||||
# Tests for DECIMAL_PLACES, ROUND_UP
|
||||
(2.34559, 2, 4, 2.3456, ROUND_UP),
|
||||
(2.34559, 2, 5, 2.34559, ROUND_UP),
|
||||
(2.34559, 2, 3, 2.346, ROUND_UP),
|
||||
(2.9999, 2, 3, 3.000, ROUND_UP),
|
||||
(2.9909, 2, 3, 2.991, ROUND_UP),
|
||||
# Tests for DECIMAL_PLACES, ROUND
|
||||
(2.345600000000001, DECIMAL_PLACES, 4, 2.3456, ROUND),
|
||||
(2.345551, DECIMAL_PLACES, 4, 2.3456, ROUND),
|
||||
(2.49, DECIMAL_PLACES, 0, 2., ROUND),
|
||||
(2.51, DECIMAL_PLACES, 0, 3., ROUND),
|
||||
(5.1, DECIMAL_PLACES, -1, 10., ROUND),
|
||||
(4.9, DECIMAL_PLACES, -1, 0., ROUND),
|
||||
# Tests for TICK_SIZE, ROUND_UP
|
||||
(2.34559, TICK_SIZE, 0.0001, 2.3456, ROUND_UP),
|
||||
(2.34559, TICK_SIZE, 0.00001, 2.34559, ROUND_UP),
|
||||
(2.34559, TICK_SIZE, 0.001, 2.346, ROUND_UP),
|
||||
(2.9999, TICK_SIZE, 0.001, 3.000, ROUND_UP),
|
||||
(2.9909, TICK_SIZE, 0.001, 2.991, ROUND_UP),
|
||||
(2.9909, TICK_SIZE, 0.005, 2.995, ROUND_UP),
|
||||
(2.9973, TICK_SIZE, 0.005, 3.0, ROUND_UP),
|
||||
(2.9977, TICK_SIZE, 0.005, 3.0, ROUND_UP),
|
||||
(234.43, TICK_SIZE, 0.5, 234.5, ROUND_UP),
|
||||
(234.53, TICK_SIZE, 0.5, 235.0, ROUND_UP),
|
||||
(0.891534, TICK_SIZE, 0.0001, 0.8916, ROUND_UP),
|
||||
(64968.89, TICK_SIZE, 0.01, 64968.89, ROUND_UP),
|
||||
(0.000000003483, TICK_SIZE, 1e-12, 0.000000003483, ROUND_UP),
|
||||
# Tests for TICK_SIZE, ROUND
|
||||
(2.49, TICK_SIZE, 1., 2., ROUND),
|
||||
(2.51, TICK_SIZE, 1., 3., ROUND),
|
||||
(2.000000051, TICK_SIZE, 0.0000001, 2.0000001, ROUND),
|
||||
(2.000000049, TICK_SIZE, 0.0000001, 2., ROUND),
|
||||
(2.9909, TICK_SIZE, 0.005, 2.990, ROUND),
|
||||
(2.9973, TICK_SIZE, 0.005, 2.995, ROUND),
|
||||
(2.9977, TICK_SIZE, 0.005, 3.0, ROUND),
|
||||
(234.24, TICK_SIZE, 0.5, 234., ROUND),
|
||||
(234.26, TICK_SIZE, 0.5, 234.5, ROUND),
|
||||
# Tests for TRUNCATTE
|
||||
(2.34559, 2, 4, 2.3455, TRUNCATE),
|
||||
(2.34559, 2, 5, 2.34559, TRUNCATE),
|
||||
(2.34559, 2, 3, 2.345, TRUNCATE),
|
||||
(2.9999, 2, 3, 2.999, TRUNCATE),
|
||||
(2.9909, 2, 3, 2.990, TRUNCATE),
|
||||
])
|
||||
def test_price_to_precision(price, precision_mode, precision, expected):
|
||||
# digits counting mode
|
||||
# DECIMAL_PLACES = 2
|
||||
# SIGNIFICANT_DIGITS = 3
|
||||
# TICK_SIZE = 4
|
||||
|
||||
assert price_to_precision(price, precision, precision_mode) == expected
|
||||
def test_price_to_precision(price, precision_mode, precision, expected, rounding_mode):
|
||||
assert price_to_precision(
|
||||
price, precision, precision_mode, rounding_mode=rounding_mode) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("price,precision_mode,precision,expected", [
|
||||
@@ -414,7 +437,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
|
||||
}
|
||||
mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets))
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
||||
expected_result = 2 * 2 * (1 + 0.05) / (1 - abs(stoploss))
|
||||
expected_result = 2 * 2 * (1 + 0.05)
|
||||
assert pytest.approx(result) == expected_result
|
||||
# With Leverage
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 5.0)
|
||||
@@ -423,14 +446,14 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
|
||||
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
|
||||
assert result == 20000
|
||||
|
||||
# min amount and cost are set (cost is minimal)
|
||||
# min amount and cost are set (cost is minimal and therefore ignored)
|
||||
markets["ETH/BTC"]["limits"] = {
|
||||
'cost': {'min': 2, 'max': None},
|
||||
'amount': {'min': 2, 'max': None},
|
||||
}
|
||||
mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets))
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
||||
expected_result = max(2, 2 * 2) * (1 + 0.05) / (1 - abs(stoploss))
|
||||
expected_result = max(2, 2 * 2) * (1 + 0.05)
|
||||
assert pytest.approx(result) == expected_result
|
||||
# With Leverage
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 10)
|
||||
@@ -473,6 +496,9 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
|
||||
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2)
|
||||
assert result == 1000
|
||||
|
||||
result = exchange.get_max_pair_stake_amount('ETH/BTC', 2, 12.0)
|
||||
assert result == 1000 / 12
|
||||
|
||||
markets["ETH/BTC"]["contractSize"] = '0.01'
|
||||
default_conf['trading_mode'] = 'futures'
|
||||
default_conf['margin_mode'] = 'isolated'
|
||||
@@ -1039,9 +1065,9 @@ def test_validate_ordertypes(default_conf, mocker):
|
||||
('bybit', 'last', True),
|
||||
('bybit', 'mark', True),
|
||||
('bybit', 'index', True),
|
||||
# ('okx', 'last', True),
|
||||
# ('okx', 'mark', True),
|
||||
# ('okx', 'index', True),
|
||||
('okx', 'last', True),
|
||||
('okx', 'mark', True),
|
||||
('okx', 'index', True),
|
||||
('gate', 'last', True),
|
||||
('gate', 'mark', True),
|
||||
('gate', 'index', True),
|
||||
@@ -1436,7 +1462,10 @@ def test_buy_prod(default_conf, mocker, exchange_name):
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == 'buy'
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
if exchange._order_needs_price(order_type):
|
||||
assert api_mock.create_order.call_args[0][4] == 200
|
||||
else:
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
order_type = 'limit'
|
||||
@@ -1541,7 +1570,10 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name):
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == 'buy'
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
if exchange._order_needs_price(order_type):
|
||||
assert api_mock.create_order.call_args[0][4] == 200
|
||||
else:
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
# Market orders should not send timeInForce!!
|
||||
assert "timeInForce" not in api_mock.create_order.call_args[0][5]
|
||||
|
||||
@@ -1585,7 +1617,10 @@ def test_sell_prod(default_conf, mocker, exchange_name):
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == 'sell'
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
if exchange._order_needs_price(order_type):
|
||||
assert api_mock.create_order.call_args[0][4] == 200
|
||||
else:
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
order_type = 'limit'
|
||||
@@ -1599,13 +1634,13 @@ def test_sell_prod(default_conf, mocker, exchange_name):
|
||||
assert api_mock.create_order.call_args[0][4] == 200
|
||||
|
||||
# test exception handling
|
||||
with pytest.raises(DependencyException):
|
||||
with pytest.raises(InsufficientFundsError):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200,
|
||||
leverage=1.0)
|
||||
|
||||
with pytest.raises(DependencyException):
|
||||
with pytest.raises(InvalidOrderException):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
exchange.create_order(pair='ETH/BTC', ordertype='limit', side="sell", amount=1, rate=200,
|
||||
@@ -1679,7 +1714,10 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name):
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == 'sell'
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
if exchange._order_needs_price(order_type):
|
||||
assert api_mock.create_order.call_args[0][4] == 200
|
||||
else:
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
# Market orders should not send timeInForce!!
|
||||
assert "timeInForce" not in api_mock.create_order.call_args[0][5]
|
||||
|
||||
@@ -2248,7 +2286,6 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach
|
||||
assert res[pair2].at[0, 'open']
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_name):
|
||||
ohlcv = [
|
||||
@@ -2277,7 +2314,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
|
||||
assert res[3] == ohlcv
|
||||
assert exchange._api_async.fetch_ohlcv.call_count == 1
|
||||
assert not log_has(f"Using cached candle (OHLCV) data for {pair} ...", caplog)
|
||||
|
||||
exchange.close()
|
||||
# exchange = Exchange(default_conf)
|
||||
await async_ccxt_exception(mocker, default_conf, MagicMock(),
|
||||
"_async_get_candle_history", "fetch_ohlcv",
|
||||
@@ -2292,15 +2329,17 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
|
||||
await exchange._async_get_candle_history(pair, "5m", CandleType.SPOT,
|
||||
(arrow.utcnow().int_timestamp - 2000) * 1000)
|
||||
|
||||
exchange.close()
|
||||
|
||||
with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching '
|
||||
r'historical candle \(OHLCV\) data\..*'):
|
||||
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
await exchange._async_get_candle_history(pair, "5m", CandleType.SPOT,
|
||||
(arrow.utcnow().int_timestamp - 2000) * 1000)
|
||||
exchange.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog):
|
||||
from freqtrade.exchange.common import _reset_logging_mixin
|
||||
_reset_logging_mixin()
|
||||
@@ -2341,9 +2380,9 @@ async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog):
|
||||
# Expect the "returned exception" message 12 times (4 retries * 3 (loop))
|
||||
assert num_log_has_re(msg, caplog) == 12
|
||||
assert num_log_has_re(msg2, caplog) == 9
|
||||
exchange.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test__async_get_candle_history_empty(default_conf, mocker, caplog):
|
||||
""" Test empty exchange result """
|
||||
ohlcv = []
|
||||
@@ -2363,6 +2402,7 @@ async def test__async_get_candle_history_empty(default_conf, mocker, caplog):
|
||||
assert res[2] == CandleType.SPOT
|
||||
assert res[3] == ohlcv
|
||||
assert exchange._api_async.fetch_ohlcv.call_count == 1
|
||||
exchange.close()
|
||||
|
||||
|
||||
def test_refresh_latest_ohlcv_inv_result(default_conf, mocker, caplog):
|
||||
@@ -2757,7 +2797,6 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na
|
||||
assert res_ohlcv[9][5] == 2.31452783
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
|
||||
fetch_trades_result):
|
||||
@@ -2785,8 +2824,8 @@ async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
|
||||
assert exchange._api_async.fetch_trades.call_args[1]['limit'] == 1000
|
||||
assert exchange._api_async.fetch_trades.call_args[1]['params'] == {'from': '123'}
|
||||
assert log_has_re(f"Fetching trades for pair {pair}, params: .*", caplog)
|
||||
exchange.close()
|
||||
|
||||
exchange = Exchange(default_conf)
|
||||
await async_ccxt_exception(mocker, default_conf, MagicMock(),
|
||||
"_async_fetch_trades", "fetch_trades",
|
||||
pair='ABCD/BTC', since=None)
|
||||
@@ -2796,15 +2835,16 @@ async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
|
||||
api_mock.fetch_trades = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
await exchange._async_fetch_trades(pair, since=(arrow.utcnow().int_timestamp - 2000) * 1000)
|
||||
exchange.close()
|
||||
|
||||
with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching '
|
||||
r'historical trade data\..*'):
|
||||
api_mock.fetch_trades = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
await exchange._async_fetch_trades(pair, since=(arrow.utcnow().int_timestamp - 2000) * 1000)
|
||||
exchange.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
async def test__async_fetch_trades_contract_size(default_conf, mocker, caplog, exchange_name,
|
||||
fetch_trades_result):
|
||||
@@ -2839,6 +2879,7 @@ async def test__async_fetch_trades_contract_size(default_conf, mocker, caplog, e
|
||||
pair = 'ETH/USDT:USDT'
|
||||
res = await exchange._async_fetch_trades(pair, since=None, params=None)
|
||||
assert res[0][5] == 300
|
||||
exchange.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -3387,7 +3428,7 @@ def test_merge_ft_has_dict(default_conf, mocker):
|
||||
ex = Binance(default_conf)
|
||||
assert ex._ft_has != Exchange._ft_has_default
|
||||
assert ex.get_option('stoploss_on_exchange')
|
||||
assert ex.get_option('order_time_in_force') == ['GTC', 'FOK', 'IOC']
|
||||
assert ex.get_option('order_time_in_force') == ['GTC', 'FOK', 'IOC', 'PO']
|
||||
assert ex.get_option('trades_pagination') == 'id'
|
||||
assert ex.get_option('trades_pagination_arg') == 'fromId'
|
||||
|
||||
@@ -3868,29 +3909,6 @@ def test_get_stake_amount_considering_leverage(
|
||||
stake_amount, leverage) == min_stake_with_lev
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name,trading_mode", [
|
||||
("binance", TradingMode.FUTURES),
|
||||
])
|
||||
def test__set_leverage(mocker, default_conf, exchange_name, trading_mode):
|
||||
|
||||
api_mock = MagicMock()
|
||||
api_mock.set_leverage = MagicMock()
|
||||
type(api_mock).has = PropertyMock(return_value={'setLeverage': True})
|
||||
default_conf['dry_run'] = False
|
||||
|
||||
ccxt_exceptionhandlers(
|
||||
mocker,
|
||||
default_conf,
|
||||
api_mock,
|
||||
exchange_name,
|
||||
"_set_leverage",
|
||||
"set_leverage",
|
||||
pair="XRP/USDT",
|
||||
leverage=5.0,
|
||||
trading_mode=trading_mode
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("margin_mode", [
|
||||
(MarginMode.CROSS),
|
||||
(MarginMode.ISOLATED)
|
||||
@@ -4830,7 +4848,6 @@ def test_load_leverage_tiers(mocker, default_conf, leverage_tiers, exchange_name
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize('exchange_name', EXCHANGES)
|
||||
async def test_get_market_leverage_tiers(mocker, default_conf, exchange_name):
|
||||
default_conf['exchange']['name'] = exchange_name
|
||||
@@ -5287,7 +5304,7 @@ def test_stoploss_contract_size(mocker, default_conf, contract_size, order_amoun
|
||||
})
|
||||
default_conf['dry_run'] = False
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.get_contract_size = MagicMock(return_value=contract_size)
|
||||
@@ -5307,3 +5324,10 @@ def test_stoploss_contract_size(mocker, default_conf, contract_size, order_amoun
|
||||
assert order['cost'] == 100
|
||||
assert order['filled'] == 100
|
||||
assert order['remaining'] == 100
|
||||
|
||||
|
||||
def test_price_to_precision_with_default_conf(default_conf, mocker):
|
||||
conf = copy.deepcopy(default_conf)
|
||||
patched_ex = get_patched_exchange(mocker, conf)
|
||||
prec_price = patched_ex.price_to_precision("XRP/USDT", 1.0000000101)
|
||||
assert prec_price == 1.00000001
|
||||
|
@@ -4,42 +4,9 @@ from unittest.mock import MagicMock
|
||||
import pytest
|
||||
|
||||
from freqtrade.enums import MarginMode, TradingMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.exchange import Gate
|
||||
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
|
||||
from tests.conftest import EXMS, get_patched_exchange
|
||||
|
||||
|
||||
def test_validate_order_types_gate(default_conf, mocker):
|
||||
default_conf['exchange']['name'] = 'gate'
|
||||
mocker.patch(f'{EXMS}._init_ccxt')
|
||||
mocker.patch(f'{EXMS}._load_markets', return_value={})
|
||||
mocker.patch(f'{EXMS}.validate_pairs')
|
||||
mocker.patch(f'{EXMS}.validate_timeframes')
|
||||
mocker.patch(f'{EXMS}.validate_stakecurrency')
|
||||
mocker.patch(f'{EXMS}.validate_pricing')
|
||||
mocker.patch(f'{EXMS}.name', 'Gate')
|
||||
exch = ExchangeResolver.load_exchange('gate', default_conf, True)
|
||||
assert isinstance(exch, Gate)
|
||||
|
||||
default_conf['order_types'] = {
|
||||
'entry': 'market',
|
||||
'exit': 'limit',
|
||||
'stoploss': 'market',
|
||||
'stoploss_on_exchange': False
|
||||
}
|
||||
|
||||
with pytest.raises(OperationalException,
|
||||
match=r'Exchange .* does not support market orders.'):
|
||||
ExchangeResolver.load_exchange('gate', default_conf, True)
|
||||
|
||||
# market-orders supported on futures markets.
|
||||
default_conf['trading_mode'] = 'futures'
|
||||
default_conf['margin_mode'] = 'isolated'
|
||||
ex = ExchangeResolver.load_exchange('gate', default_conf, True)
|
||||
assert ex
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_fetch_stoploss_order_gate(default_conf, mocker):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='gate')
|
||||
|
@@ -4,7 +4,7 @@ from unittest.mock import MagicMock
|
||||
import ccxt
|
||||
import pytest
|
||||
|
||||
from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException
|
||||
from freqtrade.exceptions import DependencyException, InvalidOrderException
|
||||
from tests.conftest import EXMS, get_patched_exchange
|
||||
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
|
||||
@@ -27,11 +27,11 @@ def test_create_stoploss_order_huobi(default_conf, mocker, limitratio, expected,
|
||||
})
|
||||
default_conf['dry_run'] = False
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
with pytest.raises(InvalidOrderException):
|
||||
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
||||
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
|
||||
side=side,
|
||||
@@ -80,11 +80,11 @@ def test_create_stoploss_order_dry_run_huobi(default_conf, mocker):
|
||||
order_type = 'stop-limit'
|
||||
default_conf['dry_run'] = True
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
with pytest.raises(InvalidOrderException):
|
||||
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
||||
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
|
||||
side='sell', leverage=1.0)
|
||||
|
@@ -29,7 +29,7 @@ def test_buy_kraken_trading_agreement(default_conf, mocker):
|
||||
default_conf['dry_run'] = False
|
||||
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
|
||||
|
||||
order = exchange.create_order(
|
||||
@@ -192,7 +192,7 @@ def test_create_stoploss_order_kraken(default_conf, mocker, ordertype, side, adj
|
||||
|
||||
default_conf['dry_run'] = False
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
||||
|
||||
@@ -263,7 +263,7 @@ def test_create_stoploss_order_dry_run_kraken(default_conf, mocker, side):
|
||||
api_mock = MagicMock()
|
||||
default_conf['dry_run'] = True
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
||||
|
||||
|
@@ -4,7 +4,7 @@ from unittest.mock import MagicMock
|
||||
import ccxt
|
||||
import pytest
|
||||
|
||||
from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException
|
||||
from freqtrade.exceptions import DependencyException, InvalidOrderException
|
||||
from tests.conftest import EXMS, get_patched_exchange
|
||||
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
|
||||
@@ -27,11 +27,11 @@ def test_create_stoploss_order_kucoin(default_conf, mocker, limitratio, expected
|
||||
})
|
||||
default_conf['dry_run'] = False
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
|
||||
if order_type == 'limit':
|
||||
with pytest.raises(OperationalException):
|
||||
with pytest.raises(InvalidOrderException):
|
||||
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
||||
order_types={
|
||||
'stoploss': order_type,
|
||||
@@ -88,11 +88,11 @@ def test_stoploss_order_dry_run_kucoin(default_conf, mocker):
|
||||
order_type = 'market'
|
||||
default_conf['dry_run'] = True
|
||||
mocker.patch(f'{EXMS}.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y: y)
|
||||
mocker.patch(f'{EXMS}.price_to_precision', lambda s, x, y, **kwargs: y)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
with pytest.raises(InvalidOrderException):
|
||||
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
||||
order_types={'stoploss': 'limit',
|
||||
'stoploss_on_exchange_limit_ratio': 1.05},
|
||||
|
@@ -2,11 +2,13 @@ from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
import ccxt
|
||||
import pytest
|
||||
|
||||
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||
from freqtrade.exceptions import RetryableOrderError
|
||||
from freqtrade.exchange.exchange import timeframe_to_minutes
|
||||
from tests.conftest import get_mock_coro, get_patched_exchange, log_has
|
||||
from tests.conftest import EXMS, get_mock_coro, get_patched_exchange, log_has
|
||||
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
|
||||
|
||||
@@ -476,3 +478,116 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets, tmpdir, caplog,
|
||||
exchange.load_leverage_tiers()
|
||||
|
||||
assert log_has(logmsg, caplog)
|
||||
|
||||
|
||||
def test__set_leverage_okx(mocker, default_conf):
|
||||
|
||||
api_mock = MagicMock()
|
||||
api_mock.set_leverage = MagicMock()
|
||||
type(api_mock).has = PropertyMock(return_value={'setLeverage': True})
|
||||
default_conf['dry_run'] = False
|
||||
default_conf['trading_mode'] = TradingMode.FUTURES
|
||||
default_conf['margin_mode'] = MarginMode.ISOLATED
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx")
|
||||
exchange._lev_prep('BTC/USDT:USDT', 3.2, 'buy')
|
||||
assert api_mock.set_leverage.call_count == 1
|
||||
# Leverage is rounded to 3.
|
||||
assert api_mock.set_leverage.call_args_list[0][1]['leverage'] == 3.2
|
||||
assert api_mock.set_leverage.call_args_list[0][1]['symbol'] == 'BTC/USDT:USDT'
|
||||
assert api_mock.set_leverage.call_args_list[0][1]['params'] == {
|
||||
'mgnMode': 'isolated',
|
||||
'posSide': 'net'}
|
||||
|
||||
ccxt_exceptionhandlers(
|
||||
mocker,
|
||||
default_conf,
|
||||
api_mock,
|
||||
"okx",
|
||||
"_lev_prep",
|
||||
"set_leverage",
|
||||
pair="XRP/USDT:USDT",
|
||||
leverage=5.0,
|
||||
side='buy'
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_fetch_stoploss_order_okx(default_conf, mocker):
|
||||
default_conf['dry_run'] = False
|
||||
api_mock = MagicMock()
|
||||
api_mock.fetch_order = MagicMock()
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='okx')
|
||||
|
||||
exchange.fetch_stoploss_order('1234', 'ETH/BTC')
|
||||
assert api_mock.fetch_order.call_count == 1
|
||||
assert api_mock.fetch_order.call_args_list[0][0][0] == '1234'
|
||||
assert api_mock.fetch_order.call_args_list[0][0][1] == 'ETH/BTC'
|
||||
assert api_mock.fetch_order.call_args_list[0][1]['params'] == {'stop': True}
|
||||
|
||||
api_mock.fetch_order = MagicMock(side_effect=ccxt.OrderNotFound)
|
||||
api_mock.fetch_open_orders = MagicMock(return_value=[])
|
||||
api_mock.fetch_closed_orders = MagicMock(return_value=[])
|
||||
api_mock.fetch_canceled_orders = MagicMock(creturn_value=[])
|
||||
|
||||
with pytest.raises(RetryableOrderError):
|
||||
exchange.fetch_stoploss_order('1234', 'ETH/BTC')
|
||||
assert api_mock.fetch_order.call_count == 1
|
||||
assert api_mock.fetch_open_orders.call_count == 1
|
||||
assert api_mock.fetch_closed_orders.call_count == 1
|
||||
assert api_mock.fetch_canceled_orders.call_count == 1
|
||||
|
||||
api_mock.fetch_order.reset_mock()
|
||||
api_mock.fetch_open_orders.reset_mock()
|
||||
api_mock.fetch_closed_orders.reset_mock()
|
||||
api_mock.fetch_canceled_orders.reset_mock()
|
||||
|
||||
api_mock.fetch_closed_orders = MagicMock(return_value=[
|
||||
{
|
||||
'id': '1234',
|
||||
'status': 'closed',
|
||||
'info': {'ordId': '123455'}
|
||||
}
|
||||
])
|
||||
mocker.patch(f"{EXMS}.fetch_order", MagicMock(return_value={'id': '123455'}))
|
||||
resp = exchange.fetch_stoploss_order('1234', 'ETH/BTC')
|
||||
assert api_mock.fetch_order.call_count == 1
|
||||
assert api_mock.fetch_open_orders.call_count == 1
|
||||
assert api_mock.fetch_closed_orders.call_count == 1
|
||||
assert api_mock.fetch_canceled_orders.call_count == 0
|
||||
|
||||
assert resp['id'] == '1234'
|
||||
assert resp['id_stop'] == '123455'
|
||||
assert resp['type'] == 'stoploss'
|
||||
|
||||
default_conf['dry_run'] = True
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='okx')
|
||||
dro_mock = mocker.patch(f"{EXMS}.fetch_dry_run_order", MagicMock(return_value={'id': '123455'}))
|
||||
|
||||
api_mock.fetch_order.reset_mock()
|
||||
api_mock.fetch_open_orders.reset_mock()
|
||||
api_mock.fetch_closed_orders.reset_mock()
|
||||
api_mock.fetch_canceled_orders.reset_mock()
|
||||
resp = exchange.fetch_stoploss_order('1234', 'ETH/BTC')
|
||||
|
||||
assert api_mock.fetch_order.call_count == 0
|
||||
assert api_mock.fetch_open_orders.call_count == 0
|
||||
assert api_mock.fetch_closed_orders.call_count == 0
|
||||
assert api_mock.fetch_canceled_orders.call_count == 0
|
||||
assert dro_mock.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize('sl1,sl2,sl3,side', [
|
||||
(1501, 1499, 1501, "sell"),
|
||||
(1499, 1501, 1499, "buy")
|
||||
])
|
||||
def test_stoploss_adjust_okx(mocker, default_conf, sl1, sl2, sl3, side):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='okx')
|
||||
order = {
|
||||
'type': 'stoploss',
|
||||
'price': 1500,
|
||||
'stopLossPrice': 1500,
|
||||
}
|
||||
assert exchange.stoploss_adjust(sl1, order, side=side)
|
||||
assert not exchange.stoploss_adjust(sl2, order, side=side)
|
||||
|
@@ -79,7 +79,9 @@ def make_rl_config(conf):
|
||||
"rr": 1,
|
||||
"profit_aim": 0.02,
|
||||
"win_reward_factor": 2
|
||||
}}
|
||||
},
|
||||
"drop_ohlc_from_features": False
|
||||
}
|
||||
|
||||
return conf
|
||||
|
||||
|
@@ -71,13 +71,6 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
|
||||
freqai_conf['freqai']['feature_parameters'].update({"shuffle_after_split": shuffle})
|
||||
freqai_conf['freqai']['feature_parameters'].update({"buffer_train_data_candles": buffer})
|
||||
|
||||
if 'ReinforcementLearner' in model:
|
||||
model_save_ext = 'zip'
|
||||
freqai_conf = make_rl_config(freqai_conf)
|
||||
# test the RL guardrails
|
||||
freqai_conf['freqai']['feature_parameters'].update({"use_SVM_to_remove_outliers": True})
|
||||
freqai_conf['freqai']['data_split_parameters'].update({'shuffle': True})
|
||||
|
||||
if 'ReinforcementLearner' in model:
|
||||
model_save_ext = 'zip'
|
||||
freqai_conf = make_rl_config(freqai_conf)
|
||||
@@ -87,6 +80,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
|
||||
|
||||
if 'test_3ac' in model or 'test_4ac' in model:
|
||||
freqai_conf["freqaimodel_path"] = str(Path(__file__).parents[1] / "freqai" / "test_models")
|
||||
freqai_conf["freqai"]["rl_config"]["drop_ohlc_from_features"] = True
|
||||
|
||||
if 'PyTorchMLPRegressor' in model:
|
||||
model_save_ext = 'zip'
|
||||
|
@@ -5,7 +5,7 @@ from unittest.mock import MagicMock
|
||||
import pytest
|
||||
|
||||
from freqtrade.data.history import get_timerange
|
||||
from freqtrade.enums import ExitType
|
||||
from freqtrade.enums import ExitType, TradingMode
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
from freqtrade.persistence.trade_model import LocalTrade
|
||||
from tests.conftest import EXMS, patch_exchange
|
||||
@@ -924,13 +924,15 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data: BTContainer)
|
||||
mocker.patch(f"{EXMS}.get_fee", return_value=0.0)
|
||||
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
|
||||
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
|
||||
mocker.patch('freqtrade.exchange.binance.Binance.get_max_leverage', return_value=100)
|
||||
mocker.patch(f"{EXMS}.get_max_leverage", return_value=100)
|
||||
mocker.patch(f"{EXMS}.calculate_funding_fees", return_value=0)
|
||||
patch_exchange(mocker)
|
||||
frame = _build_backtest_dataframe(data.data)
|
||||
backtesting = Backtesting(default_conf)
|
||||
# TODO: Should we initialize this properly??
|
||||
backtesting._can_short = True
|
||||
backtesting.trading_mode = TradingMode.MARGIN
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
backtesting._can_short = True
|
||||
backtesting.required_startup = 0
|
||||
backtesting.strategy.advise_entry = lambda a, m: frame
|
||||
backtesting.strategy.advise_exit = lambda a, m: frame
|
||||
|
@@ -344,7 +344,7 @@ def test_backtest_abort(default_conf, mocker, testdatadir) -> None:
|
||||
assert backtesting.progress.progress == 0
|
||||
|
||||
|
||||
def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
|
||||
def test_backtesting_start(default_conf, mocker, caplog) -> None:
|
||||
def get_timerange(input1):
|
||||
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
|
||||
|
||||
@@ -367,6 +367,7 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
backtesting.strategy.bot_loop_start = MagicMock()
|
||||
backtesting.strategy.bot_start = MagicMock()
|
||||
backtesting.start()
|
||||
# check the logs, that will contain the backtest result
|
||||
exists = [
|
||||
@@ -376,7 +377,8 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
|
||||
for line in exists:
|
||||
assert log_has(line, caplog)
|
||||
assert backtesting.strategy.dp._pairlists is not None
|
||||
assert backtesting.strategy.bot_loop_start.call_count == 1
|
||||
assert backtesting.strategy.bot_start.call_count == 1
|
||||
assert backtesting.strategy.bot_loop_start.call_count == 0
|
||||
assert sbs.call_count == 1
|
||||
assert sbc.call_count == 1
|
||||
|
||||
|
@@ -10,7 +10,7 @@ from arrow import Arrow
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.data import history
|
||||
from freqtrade.data.history import get_timerange
|
||||
from freqtrade.enums import ExitType
|
||||
from freqtrade.enums import ExitType, TradingMode
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
from tests.conftest import EXMS, patch_exchange
|
||||
|
||||
@@ -108,9 +108,10 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
|
||||
default_conf.update({
|
||||
"stake_amount": 100.0,
|
||||
"dry_run_wallet": 1000.0,
|
||||
"strategy": "StrategyTestV3"
|
||||
"strategy": "StrategyTestV3",
|
||||
})
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting.trading_mode = TradingMode.FUTURES
|
||||
backtesting._can_short = True
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
pair = 'XRP/USDT'
|
||||
|
@@ -872,7 +872,8 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
|
||||
hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
|
||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
|
||||
assert hyperopt.backtesting.strategy.bot_loop_started is True
|
||||
assert hyperopt.backtesting.strategy.bot_started is True
|
||||
assert hyperopt.backtesting.strategy.bot_loop_started is False
|
||||
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
|
||||
@@ -922,7 +923,8 @@ def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmpdir,
|
||||
|
||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
|
||||
assert hyperopt.backtesting.strategy.bot_loop_started is True
|
||||
assert hyperopt.backtesting.strategy.bot_started is True
|
||||
assert hyperopt.backtesting.strategy.bot_loop_started is False
|
||||
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
|
||||
@@ -959,7 +961,8 @@ def test_in_strategy_auto_hyperopt_per_epoch(mocker, hyperopt_conf, tmpdir, fee)
|
||||
hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
|
||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
|
||||
assert hyperopt.backtesting.strategy.bot_loop_started is True
|
||||
assert hyperopt.backtesting.strategy.bot_loop_started is False
|
||||
assert hyperopt.backtesting.strategy.bot_started is True
|
||||
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
|
||||
|
@@ -236,7 +236,7 @@ def test_store_backtest_candles(testdatadir, mocker):
|
||||
|
||||
assert dump_mock.call_count == 1
|
||||
assert isinstance(dump_mock.call_args_list[0][0][0], Path)
|
||||
assert str(dump_mock.call_args_list[0][0][0]).endswith(str('_signals.pkl'))
|
||||
assert str(dump_mock.call_args_list[0][0][0]).endswith('_signals.pkl')
|
||||
|
||||
dump_mock.reset_mock()
|
||||
# mock file exporting
|
||||
@@ -245,7 +245,7 @@ def test_store_backtest_candles(testdatadir, mocker):
|
||||
assert dump_mock.call_count == 1
|
||||
assert isinstance(dump_mock.call_args_list[0][0][0], Path)
|
||||
# result will be testdatadir / testresult-<timestamp>_signals.pkl
|
||||
assert str(dump_mock.call_args_list[0][0][0]).endswith(str('_signals.pkl'))
|
||||
assert str(dump_mock.call_args_list[0][0][0]).endswith('_signals.pkl')
|
||||
dump_mock.reset_mock()
|
||||
|
||||
|
||||
|
@@ -4,7 +4,7 @@ from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import create_engine, text
|
||||
from sqlalchemy import create_engine, select, text
|
||||
|
||||
from freqtrade.constants import DEFAULT_DB_PROD_URL
|
||||
from freqtrade.enums import TradingMode
|
||||
@@ -21,8 +21,8 @@ spot, margin, futures = TradingMode.SPOT, TradingMode.MARGIN, TradingMode.FUTURE
|
||||
def test_init_create_session(default_conf):
|
||||
# Check if init create a session
|
||||
init_db(default_conf['db_url'])
|
||||
assert hasattr(Trade, '_session')
|
||||
assert 'scoped_session' in type(Trade._session).__name__
|
||||
assert hasattr(Trade, 'session')
|
||||
assert 'scoped_session' in type(Trade.session).__name__
|
||||
|
||||
|
||||
def test_init_custom_db_url(default_conf, tmpdir):
|
||||
@@ -34,7 +34,7 @@ def test_init_custom_db_url(default_conf, tmpdir):
|
||||
|
||||
init_db(default_conf['db_url'])
|
||||
assert Path(filename).is_file()
|
||||
r = Trade._session.execute(text("PRAGMA journal_mode"))
|
||||
r = Trade.session.execute(text("PRAGMA journal_mode"))
|
||||
assert r.first() == ('wal',)
|
||||
|
||||
|
||||
@@ -235,8 +235,9 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||
# Run init to test migration
|
||||
init_db(default_conf['db_url'])
|
||||
|
||||
assert len(Trade.query.filter(Trade.id == 1).all()) == 1
|
||||
trade = Trade.query.filter(Trade.id == 1).first()
|
||||
trades = Trade.session.scalars(select(Trade).filter(Trade.id == 1)).all()
|
||||
assert len(trades) == 1
|
||||
trade = trades[0]
|
||||
assert trade.fee_open == fee.return_value
|
||||
assert trade.fee_close == fee.return_value
|
||||
assert trade.open_rate_requested is None
|
||||
@@ -404,9 +405,9 @@ def test_migrate_pairlocks(mocker, default_conf, fee, caplog):
|
||||
|
||||
init_db(default_conf['db_url'])
|
||||
|
||||
assert len(PairLock.query.all()) == 2
|
||||
assert len(PairLock.query.filter(PairLock.pair == '*').all()) == 1
|
||||
pairlocks = PairLock.query.filter(PairLock.pair == 'ETH/BTC').all()
|
||||
assert len(PairLock.get_all_locks().all()) == 2
|
||||
assert len(PairLock.session.scalars(select(PairLock).filter(PairLock.pair == '*')).all()) == 1
|
||||
pairlocks = PairLock.session.scalars(select(PairLock).filter(PairLock.pair == 'ETH/BTC')).all()
|
||||
assert len(pairlocks) == 1
|
||||
pairlocks[0].pair == 'ETH/BTC'
|
||||
pairlocks[0].side == '*'
|
||||
|
@@ -4,6 +4,7 @@ from types import FunctionType
|
||||
|
||||
import arrow
|
||||
import pytest
|
||||
from sqlalchemy import select
|
||||
|
||||
from freqtrade.constants import DATETIME_PRINT_FORMAT
|
||||
from freqtrade.enums import TradingMode
|
||||
@@ -1329,71 +1330,78 @@ def test_to_json(fee):
|
||||
open_rate=0.123,
|
||||
exchange='binance',
|
||||
enter_tag=None,
|
||||
open_order_id='dry_run_buy_12345'
|
||||
open_order_id='dry_run_buy_12345',
|
||||
precision_mode=1,
|
||||
amount_precision=8.0,
|
||||
price_precision=7.0,
|
||||
)
|
||||
result = trade.to_json()
|
||||
assert isinstance(result, dict)
|
||||
|
||||
assert result == {'trade_id': None,
|
||||
'pair': 'ADA/USDT',
|
||||
'base_currency': 'ADA',
|
||||
'quote_currency': 'USDT',
|
||||
'is_open': None,
|
||||
'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT),
|
||||
'open_timestamp': int(trade.open_date.timestamp() * 1000),
|
||||
'open_order_id': 'dry_run_buy_12345',
|
||||
'close_date': None,
|
||||
'close_timestamp': None,
|
||||
'open_rate': 0.123,
|
||||
'open_rate_requested': None,
|
||||
'open_trade_value': 15.1668225,
|
||||
'fee_close': 0.0025,
|
||||
'fee_close_cost': None,
|
||||
'fee_close_currency': None,
|
||||
'fee_open': 0.0025,
|
||||
'fee_open_cost': None,
|
||||
'fee_open_currency': None,
|
||||
'close_rate': None,
|
||||
'close_rate_requested': None,
|
||||
'amount': 123.0,
|
||||
'amount_requested': 123.0,
|
||||
'stake_amount': 0.001,
|
||||
'max_stake_amount': None,
|
||||
'trade_duration': None,
|
||||
'trade_duration_s': None,
|
||||
'realized_profit': 0.0,
|
||||
'realized_profit_ratio': None,
|
||||
'close_profit': None,
|
||||
'close_profit_pct': None,
|
||||
'close_profit_abs': None,
|
||||
'profit_ratio': None,
|
||||
'profit_pct': None,
|
||||
'profit_abs': None,
|
||||
'exit_reason': None,
|
||||
'exit_order_status': None,
|
||||
'stop_loss_abs': None,
|
||||
'stop_loss_ratio': None,
|
||||
'stop_loss_pct': None,
|
||||
'stoploss_order_id': None,
|
||||
'stoploss_last_update': None,
|
||||
'stoploss_last_update_timestamp': None,
|
||||
'initial_stop_loss_abs': None,
|
||||
'initial_stop_loss_pct': None,
|
||||
'initial_stop_loss_ratio': None,
|
||||
'min_rate': None,
|
||||
'max_rate': None,
|
||||
'strategy': None,
|
||||
'enter_tag': None,
|
||||
'timeframe': None,
|
||||
'exchange': 'binance',
|
||||
'leverage': None,
|
||||
'interest_rate': None,
|
||||
'liquidation_price': None,
|
||||
'is_short': None,
|
||||
'trading_mode': None,
|
||||
'funding_fees': None,
|
||||
'orders': [],
|
||||
}
|
||||
assert result == {
|
||||
'trade_id': None,
|
||||
'pair': 'ADA/USDT',
|
||||
'base_currency': 'ADA',
|
||||
'quote_currency': 'USDT',
|
||||
'is_open': None,
|
||||
'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT),
|
||||
'open_timestamp': int(trade.open_date.timestamp() * 1000),
|
||||
'open_order_id': 'dry_run_buy_12345',
|
||||
'close_date': None,
|
||||
'close_timestamp': None,
|
||||
'open_rate': 0.123,
|
||||
'open_rate_requested': None,
|
||||
'open_trade_value': 15.1668225,
|
||||
'fee_close': 0.0025,
|
||||
'fee_close_cost': None,
|
||||
'fee_close_currency': None,
|
||||
'fee_open': 0.0025,
|
||||
'fee_open_cost': None,
|
||||
'fee_open_currency': None,
|
||||
'close_rate': None,
|
||||
'close_rate_requested': None,
|
||||
'amount': 123.0,
|
||||
'amount_requested': 123.0,
|
||||
'stake_amount': 0.001,
|
||||
'max_stake_amount': None,
|
||||
'trade_duration': None,
|
||||
'trade_duration_s': None,
|
||||
'realized_profit': 0.0,
|
||||
'realized_profit_ratio': None,
|
||||
'close_profit': None,
|
||||
'close_profit_pct': None,
|
||||
'close_profit_abs': None,
|
||||
'profit_ratio': None,
|
||||
'profit_pct': None,
|
||||
'profit_abs': None,
|
||||
'exit_reason': None,
|
||||
'exit_order_status': None,
|
||||
'stop_loss_abs': None,
|
||||
'stop_loss_ratio': None,
|
||||
'stop_loss_pct': None,
|
||||
'stoploss_order_id': None,
|
||||
'stoploss_last_update': None,
|
||||
'stoploss_last_update_timestamp': None,
|
||||
'initial_stop_loss_abs': None,
|
||||
'initial_stop_loss_pct': None,
|
||||
'initial_stop_loss_ratio': None,
|
||||
'min_rate': None,
|
||||
'max_rate': None,
|
||||
'strategy': None,
|
||||
'enter_tag': None,
|
||||
'timeframe': None,
|
||||
'exchange': 'binance',
|
||||
'leverage': None,
|
||||
'interest_rate': None,
|
||||
'liquidation_price': None,
|
||||
'is_short': None,
|
||||
'trading_mode': None,
|
||||
'funding_fees': None,
|
||||
'amount_precision': 8.0,
|
||||
'price_precision': 7.0,
|
||||
'precision_mode': 1,
|
||||
'orders': [],
|
||||
}
|
||||
|
||||
# Simulate dry_run entries
|
||||
trade = Trade(
|
||||
@@ -1409,70 +1417,77 @@ def test_to_json(fee):
|
||||
close_rate=0.125,
|
||||
enter_tag='buys_signal_001',
|
||||
exchange='binance',
|
||||
precision_mode=2,
|
||||
amount_precision=7.0,
|
||||
price_precision=8.0,
|
||||
)
|
||||
result = trade.to_json()
|
||||
assert isinstance(result, dict)
|
||||
|
||||
assert result == {'trade_id': None,
|
||||
'pair': 'XRP/BTC',
|
||||
'base_currency': 'XRP',
|
||||
'quote_currency': 'BTC',
|
||||
'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT),
|
||||
'open_timestamp': int(trade.open_date.timestamp() * 1000),
|
||||
'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT),
|
||||
'close_timestamp': int(trade.close_date.timestamp() * 1000),
|
||||
'open_rate': 0.123,
|
||||
'close_rate': 0.125,
|
||||
'amount': 100.0,
|
||||
'amount_requested': 101.0,
|
||||
'stake_amount': 0.001,
|
||||
'max_stake_amount': None,
|
||||
'trade_duration': 60,
|
||||
'trade_duration_s': 3600,
|
||||
'stop_loss_abs': None,
|
||||
'stop_loss_pct': None,
|
||||
'stop_loss_ratio': None,
|
||||
'stoploss_order_id': None,
|
||||
'stoploss_last_update': None,
|
||||
'stoploss_last_update_timestamp': None,
|
||||
'initial_stop_loss_abs': None,
|
||||
'initial_stop_loss_pct': None,
|
||||
'initial_stop_loss_ratio': None,
|
||||
'realized_profit': 0.0,
|
||||
'realized_profit_ratio': None,
|
||||
'close_profit': None,
|
||||
'close_profit_pct': None,
|
||||
'close_profit_abs': None,
|
||||
'profit_ratio': None,
|
||||
'profit_pct': None,
|
||||
'profit_abs': None,
|
||||
'close_rate_requested': None,
|
||||
'fee_close': 0.0025,
|
||||
'fee_close_cost': None,
|
||||
'fee_close_currency': None,
|
||||
'fee_open': 0.0025,
|
||||
'fee_open_cost': None,
|
||||
'fee_open_currency': None,
|
||||
'is_open': None,
|
||||
'max_rate': None,
|
||||
'min_rate': None,
|
||||
'open_order_id': None,
|
||||
'open_rate_requested': None,
|
||||
'open_trade_value': 12.33075,
|
||||
'exit_reason': None,
|
||||
'exit_order_status': None,
|
||||
'strategy': None,
|
||||
'enter_tag': 'buys_signal_001',
|
||||
'timeframe': None,
|
||||
'exchange': 'binance',
|
||||
'leverage': None,
|
||||
'interest_rate': None,
|
||||
'liquidation_price': None,
|
||||
'is_short': None,
|
||||
'trading_mode': None,
|
||||
'funding_fees': None,
|
||||
'orders': [],
|
||||
}
|
||||
assert result == {
|
||||
'trade_id': None,
|
||||
'pair': 'XRP/BTC',
|
||||
'base_currency': 'XRP',
|
||||
'quote_currency': 'BTC',
|
||||
'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT),
|
||||
'open_timestamp': int(trade.open_date.timestamp() * 1000),
|
||||
'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT),
|
||||
'close_timestamp': int(trade.close_date.timestamp() * 1000),
|
||||
'open_rate': 0.123,
|
||||
'close_rate': 0.125,
|
||||
'amount': 100.0,
|
||||
'amount_requested': 101.0,
|
||||
'stake_amount': 0.001,
|
||||
'max_stake_amount': None,
|
||||
'trade_duration': 60,
|
||||
'trade_duration_s': 3600,
|
||||
'stop_loss_abs': None,
|
||||
'stop_loss_pct': None,
|
||||
'stop_loss_ratio': None,
|
||||
'stoploss_order_id': None,
|
||||
'stoploss_last_update': None,
|
||||
'stoploss_last_update_timestamp': None,
|
||||
'initial_stop_loss_abs': None,
|
||||
'initial_stop_loss_pct': None,
|
||||
'initial_stop_loss_ratio': None,
|
||||
'realized_profit': 0.0,
|
||||
'realized_profit_ratio': None,
|
||||
'close_profit': None,
|
||||
'close_profit_pct': None,
|
||||
'close_profit_abs': None,
|
||||
'profit_ratio': None,
|
||||
'profit_pct': None,
|
||||
'profit_abs': None,
|
||||
'close_rate_requested': None,
|
||||
'fee_close': 0.0025,
|
||||
'fee_close_cost': None,
|
||||
'fee_close_currency': None,
|
||||
'fee_open': 0.0025,
|
||||
'fee_open_cost': None,
|
||||
'fee_open_currency': None,
|
||||
'is_open': None,
|
||||
'max_rate': None,
|
||||
'min_rate': None,
|
||||
'open_order_id': None,
|
||||
'open_rate_requested': None,
|
||||
'open_trade_value': 12.33075,
|
||||
'exit_reason': None,
|
||||
'exit_order_status': None,
|
||||
'strategy': None,
|
||||
'enter_tag': 'buys_signal_001',
|
||||
'timeframe': None,
|
||||
'exchange': 'binance',
|
||||
'leverage': None,
|
||||
'interest_rate': None,
|
||||
'liquidation_price': None,
|
||||
'is_short': None,
|
||||
'trading_mode': None,
|
||||
'funding_fees': None,
|
||||
'amount_precision': 7.0,
|
||||
'price_precision': 8.0,
|
||||
'precision_mode': 2,
|
||||
'orders': [],
|
||||
}
|
||||
|
||||
|
||||
def test_stoploss_reinitialization(default_conf, fee):
|
||||
@@ -1494,7 +1509,7 @@ def test_stoploss_reinitialization(default_conf, fee):
|
||||
assert trade.stop_loss_pct == -0.05
|
||||
assert trade.initial_stop_loss == 0.95
|
||||
assert trade.initial_stop_loss_pct == -0.05
|
||||
Trade.query.session.add(trade)
|
||||
Trade.session.add(trade)
|
||||
Trade.commit()
|
||||
|
||||
# Lower stoploss
|
||||
@@ -1556,7 +1571,7 @@ def test_stoploss_reinitialization_leverage(default_conf, fee):
|
||||
assert trade.stop_loss_pct == -0.1
|
||||
assert trade.initial_stop_loss == 0.98
|
||||
assert trade.initial_stop_loss_pct == -0.1
|
||||
Trade.query.session.add(trade)
|
||||
Trade.session.add(trade)
|
||||
Trade.commit()
|
||||
|
||||
# Lower stoploss
|
||||
@@ -1618,7 +1633,7 @@ def test_stoploss_reinitialization_short(default_conf, fee):
|
||||
assert trade.stop_loss_pct == -0.1
|
||||
assert trade.initial_stop_loss == 1.02
|
||||
assert trade.initial_stop_loss_pct == -0.1
|
||||
Trade.query.session.add(trade)
|
||||
Trade.session.add(trade)
|
||||
Trade.commit()
|
||||
# Lower stoploss
|
||||
Trade.stoploss_reinitialization(-0.15)
|
||||
@@ -1793,17 +1808,17 @@ def test_get_trades_proxy(fee, use_db, is_short):
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
@pytest.mark.parametrize('is_short', [True, False])
|
||||
def test_get_trades__query(fee, is_short):
|
||||
query = Trade.get_trades([])
|
||||
query = Trade.get_trades_query([])
|
||||
# without orders there should be no join issued.
|
||||
query1 = Trade.get_trades([], include_orders=False)
|
||||
query1 = Trade.get_trades_query([], include_orders=False)
|
||||
|
||||
# Empty "with-options -> default - selectin"
|
||||
assert query._with_options == ()
|
||||
assert query1._with_options != ()
|
||||
|
||||
create_mock_trades(fee, is_short)
|
||||
query = Trade.get_trades([])
|
||||
query1 = Trade.get_trades([], include_orders=False)
|
||||
query = Trade.get_trades_query([])
|
||||
query1 = Trade.get_trades_query([], include_orders=False)
|
||||
|
||||
assert query._with_options == ()
|
||||
assert query1._with_options != ()
|
||||
@@ -2016,6 +2031,7 @@ def test_Trade_object_idem():
|
||||
'get_open_trades_without_assigned_fees',
|
||||
'get_open_order_trades',
|
||||
'get_trades',
|
||||
'get_trades_query',
|
||||
'get_exit_reason_performance',
|
||||
'get_enter_tag_performance',
|
||||
'get_mix_tag_performance',
|
||||
@@ -2443,8 +2459,8 @@ def test_order_to_ccxt(limit_buy_order_open):
|
||||
|
||||
order = Order.parse_from_ccxt_object(limit_buy_order_open, 'mocked', 'buy')
|
||||
order.ft_trade_id = 1
|
||||
order.query.session.add(order)
|
||||
Order.query.session.commit()
|
||||
order.session.add(order)
|
||||
Order.session.commit()
|
||||
|
||||
order_resp = Order.order_by_id(limit_buy_order_open['id'])
|
||||
assert order_resp
|
||||
@@ -2546,7 +2562,7 @@ def test_recalc_trade_from_orders_dca(data) -> None:
|
||||
leverage=1.0,
|
||||
trading_mode=TradingMode.SPOT
|
||||
)
|
||||
Trade.query.session.add(trade)
|
||||
Trade.session.add(trade)
|
||||
|
||||
for idx, (order, result) in enumerate(data['orders']):
|
||||
amount = order[1]
|
||||
@@ -2575,11 +2591,11 @@ def test_recalc_trade_from_orders_dca(data) -> None:
|
||||
trade.recalc_trade_from_orders()
|
||||
Trade.commit()
|
||||
|
||||
orders1 = Order.query.all()
|
||||
orders1 = Order.session.scalars(select(Order)).all()
|
||||
assert orders1
|
||||
assert len(orders1) == idx + 1
|
||||
|
||||
trade = Trade.query.first()
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade
|
||||
assert len(trade.orders) == idx + 1
|
||||
if idx < len(data) - 1:
|
||||
@@ -2596,6 +2612,6 @@ def test_recalc_trade_from_orders_dca(data) -> None:
|
||||
assert pytest.approx(trade.close_profit_abs) == data['end_profit']
|
||||
assert pytest.approx(trade.close_profit) == data['end_profit_ratio']
|
||||
assert not trade.is_open
|
||||
trade = Trade.query.first()
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade
|
||||
assert trade.open_order_id is None
|
||||
|
@@ -50,8 +50,8 @@ def test_trade_fromjson():
|
||||
"stop_loss_ratio": -0.216,
|
||||
"stop_loss_pct": -21.6,
|
||||
"stoploss_order_id": null,
|
||||
"stoploss_last_update": null,
|
||||
"stoploss_last_update_timestamp": null,
|
||||
"stoploss_last_update": "2022-10-18 09:13:42",
|
||||
"stoploss_last_update_timestamp": 1666077222000,
|
||||
"initial_stop_loss_abs": 0.1981,
|
||||
"initial_stop_loss_ratio": -0.216,
|
||||
"initial_stop_loss_pct": -21.6,
|
||||
|
@@ -711,8 +711,8 @@ def test_PrecisionFilter_error(mocker, whitelist_conf) -> None:
|
||||
|
||||
def test_PerformanceFilter_error(mocker, whitelist_conf, caplog) -> None:
|
||||
whitelist_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PerformanceFilter"}]
|
||||
if hasattr(Trade, 'query'):
|
||||
del Trade.query
|
||||
if hasattr(Trade, 'session'):
|
||||
del Trade.session
|
||||
mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True))
|
||||
exchange = get_patched_exchange(mocker, whitelist_conf)
|
||||
pm = PairListManager(exchange, whitelist_conf, MagicMock())
|
||||
@@ -828,6 +828,12 @@ def test_pair_whitelist_not_supported_Spread(mocker, default_conf, tickers) -> N
|
||||
match=r'Exchange does not support fetchTickers, .*'):
|
||||
get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True))
|
||||
mocker.patch(f'{EXMS}.get_option', MagicMock(return_value=False))
|
||||
with pytest.raises(OperationalException,
|
||||
match=r'.*requires exchange to have bid/ask data'):
|
||||
get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("pairlist", TESTABLE_PAIRLISTS)
|
||||
def test_pairlist_class(mocker, whitelist_conf, markets, pairlist):
|
||||
|
@@ -14,7 +14,7 @@ def test_PairLocks(use_db):
|
||||
PairLocks.use_db = use_db
|
||||
# No lock should be present
|
||||
if use_db:
|
||||
assert len(PairLock.query.all()) == 0
|
||||
assert len(PairLock.get_all_locks().all()) == 0
|
||||
|
||||
assert PairLocks.use_db == use_db
|
||||
|
||||
@@ -88,13 +88,13 @@ def test_PairLocks(use_db):
|
||||
|
||||
if use_db:
|
||||
locks = PairLocks.get_all_locks()
|
||||
locks_db = PairLock.query.all()
|
||||
locks_db = PairLock.get_all_locks().all()
|
||||
assert len(locks) == len(locks_db)
|
||||
assert len(locks_db) > 0
|
||||
else:
|
||||
# Nothing was pushed to the database
|
||||
assert len(PairLocks.get_all_locks()) > 0
|
||||
assert len(PairLock.query.all()) == 0
|
||||
assert len(PairLock.get_all_locks().all()) == 0
|
||||
# Reset use-db variable
|
||||
PairLocks.reset_locks()
|
||||
PairLocks.use_db = True
|
||||
@@ -107,7 +107,7 @@ def test_PairLocks_getlongestlock(use_db):
|
||||
# No lock should be present
|
||||
PairLocks.use_db = use_db
|
||||
if use_db:
|
||||
assert len(PairLock.query.all()) == 0
|
||||
assert len(PairLock.get_all_locks().all()) == 0
|
||||
|
||||
assert PairLocks.use_db == use_db
|
||||
|
||||
@@ -139,7 +139,7 @@ def test_PairLocks_reason(use_db):
|
||||
PairLocks.use_db = use_db
|
||||
# No lock should be present
|
||||
if use_db:
|
||||
assert len(PairLock.query.all()) == 0
|
||||
assert len(PairLock.get_all_locks().all()) == 0
|
||||
|
||||
assert PairLocks.use_db == use_db
|
||||
|
||||
|
@@ -74,7 +74,7 @@ def generate_mock_trade(pair: str, fee: float, is_open: bool,
|
||||
trade.close(close_price)
|
||||
trade.exit_reason = exit_reason
|
||||
|
||||
Trade.query.session.add(trade)
|
||||
Trade.session.add(trade)
|
||||
Trade.commit()
|
||||
return trade
|
||||
|
||||
|
@@ -4,6 +4,7 @@ from unittest.mock import ANY, MagicMock, PropertyMock
|
||||
|
||||
import pytest
|
||||
from numpy import isnan
|
||||
from sqlalchemy import select
|
||||
|
||||
from freqtrade.edge import PairInfo
|
||||
from freqtrade.enums import SignalDirection, State, TradingMode
|
||||
@@ -50,7 +51,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'amount': 91.07468123,
|
||||
'amount_requested': 91.07468124,
|
||||
'stake_amount': 0.001,
|
||||
'max_stake_amount': ANY,
|
||||
'max_stake_amount': None,
|
||||
'trade_duration': None,
|
||||
'trade_duration_s': None,
|
||||
'close_profit': None,
|
||||
@@ -79,6 +80,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'realized_profit_ratio': None,
|
||||
'total_profit_abs': -4.09e-06,
|
||||
'total_profit_fiat': ANY,
|
||||
'total_profit_ratio': None,
|
||||
'exchange': 'binance',
|
||||
'leverage': 1.0,
|
||||
'interest_rate': 0.0,
|
||||
@@ -86,6 +88,9 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'is_short': False,
|
||||
'funding_fees': 0.0,
|
||||
'trading_mode': TradingMode.SPOT,
|
||||
'amount_precision': 8.0,
|
||||
'price_precision': 8.0,
|
||||
'precision_mode': 2,
|
||||
'orders': [{
|
||||
'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05,
|
||||
'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy',
|
||||
@@ -123,17 +128,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'profit_pct': 0.0,
|
||||
'profit_abs': 0.0,
|
||||
'total_profit_abs': 0.0,
|
||||
'stop_loss_abs': 0.0,
|
||||
'stop_loss_pct': None,
|
||||
'stop_loss_ratio': None,
|
||||
'stoploss_current_dist': -1.099e-05,
|
||||
'stoploss_current_dist_ratio': -1.0,
|
||||
'stoploss_current_dist_pct': pytest.approx(-100.0),
|
||||
'stoploss_entry_dist': -0.0010025,
|
||||
'stoploss_entry_dist_ratio': -1.0,
|
||||
'initial_stop_loss_abs': 0.0,
|
||||
'initial_stop_loss_pct': None,
|
||||
'initial_stop_loss_ratio': None,
|
||||
'open_order': '(limit buy rem=91.07468123)',
|
||||
})
|
||||
response_unfilled['orders'][0].update({
|
||||
@@ -168,6 +162,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
results = rpc._rpc_trade_status()
|
||||
|
||||
response = deepcopy(gen_response)
|
||||
response.update({
|
||||
'max_stake_amount': 0.001,
|
||||
'total_profit_ratio': pytest.approx(-0.00409),
|
||||
})
|
||||
assert results[0] == response
|
||||
|
||||
mocker.patch(f'{EXMS}.get_rate',
|
||||
@@ -181,10 +179,12 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'stoploss_current_dist': ANY,
|
||||
'stoploss_current_dist_ratio': ANY,
|
||||
'stoploss_current_dist_pct': ANY,
|
||||
'max_stake_amount': 0.001,
|
||||
'profit_ratio': ANY,
|
||||
'profit_pct': ANY,
|
||||
'profit_abs': ANY,
|
||||
'total_profit_abs': ANY,
|
||||
'total_profit_ratio': ANY,
|
||||
'current_rate': ANY,
|
||||
})
|
||||
assert results[0] == response_norate
|
||||
@@ -347,7 +347,7 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short):
|
||||
with pytest.raises(RPCException, match='invalid argument'):
|
||||
rpc._rpc_delete('200')
|
||||
|
||||
trades = Trade.query.all()
|
||||
trades = Trade.session.scalars(select(Trade)).all()
|
||||
trades[1].stoploss_order_id = '1234'
|
||||
trades[2].stoploss_order_id = '1234'
|
||||
assert len(trades) > 2
|
||||
@@ -710,7 +710,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
|
||||
mocker.patch(f'{EXMS}._dry_is_price_crossed', MagicMock(return_value=False))
|
||||
freqtradebot.enter_positions()
|
||||
# make an limit-buy open trade
|
||||
trade = Trade.query.filter(Trade.id == '3').first()
|
||||
trade = Trade.session.scalars(select(Trade).filter(Trade.id == '3')).first()
|
||||
filled_amount = trade.amount / 2
|
||||
# Fetch order - it's open first, and closed after cancel_order is called.
|
||||
mocker.patch(
|
||||
@@ -746,7 +746,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
|
||||
|
||||
freqtradebot.config['max_open_trades'] = 3
|
||||
freqtradebot.enter_positions()
|
||||
trade = Trade.query.filter(Trade.id == '2').first()
|
||||
trade = Trade.session.scalars(select(Trade).filter(Trade.id == '2')).first()
|
||||
amount = trade.amount
|
||||
# make an limit-buy open trade, if there is no 'filled', don't sell it
|
||||
mocker.patch(
|
||||
@@ -764,7 +764,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
|
||||
assert cancel_order_mock.call_count == 2
|
||||
assert trade.amount == amount
|
||||
|
||||
trade = Trade.query.filter(Trade.id == '3').first()
|
||||
trade = Trade.session.scalars(select(Trade).filter(Trade.id == '3')).first()
|
||||
|
||||
# make an limit-sell open trade
|
||||
mocker.patch(
|
||||
|
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Unit test file for rpc/api_server.py
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import time
|
||||
from datetime import datetime, timedelta, timezone
|
||||
@@ -14,6 +15,7 @@ from fastapi import FastAPI, WebSocketDisconnect
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi.testclient import TestClient
|
||||
from requests.auth import _basic_auth_str
|
||||
from sqlalchemy import select
|
||||
|
||||
from freqtrade.__init__ import __version__
|
||||
from freqtrade.enums import CandleType, RunMode, State, TradingMode
|
||||
@@ -298,10 +300,6 @@ def test_api_UvicornServer(mocker):
|
||||
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1'))
|
||||
assert thread_mock.call_count == 0
|
||||
|
||||
s.install_signal_handlers()
|
||||
# Original implementation starts a thread - make sure that's not the case
|
||||
assert thread_mock.call_count == 0
|
||||
|
||||
# Fake started to avoid sleeping forever
|
||||
s.started = True
|
||||
s.run_in_thread()
|
||||
@@ -317,10 +315,6 @@ def test_api_UvicornServer_run(mocker):
|
||||
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1'))
|
||||
assert serve_mock.call_count == 0
|
||||
|
||||
s.install_signal_handlers()
|
||||
# Original implementation starts a thread - make sure that's not the case
|
||||
assert serve_mock.call_count == 0
|
||||
|
||||
# Fake started to avoid sleeping forever
|
||||
s.started = True
|
||||
s.run()
|
||||
@@ -330,13 +324,10 @@ def test_api_UvicornServer_run(mocker):
|
||||
def test_api_UvicornServer_run_no_uvloop(mocker, import_fails):
|
||||
serve_mock = mocker.patch('freqtrade.rpc.api_server.uvicorn_threaded.UvicornServer.serve',
|
||||
get_mock_coro(None))
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1'))
|
||||
assert serve_mock.call_count == 0
|
||||
|
||||
s.install_signal_handlers()
|
||||
# Original implementation starts a thread - make sure that's not the case
|
||||
assert serve_mock.call_count == 0
|
||||
|
||||
# Fake started to avoid sleeping forever
|
||||
s.started = True
|
||||
s.run()
|
||||
@@ -624,7 +615,7 @@ def test_api_trades(botclient, mocker, fee, markets, is_short):
|
||||
assert rc.json()['offset'] == 0
|
||||
|
||||
create_mock_trades(fee, is_short=is_short)
|
||||
Trade.query.session.flush()
|
||||
Trade.session.flush()
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/trades")
|
||||
assert_response(rc)
|
||||
@@ -652,7 +643,7 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets, is_short):
|
||||
assert_response(rc, 404)
|
||||
assert rc.json()['detail'] == 'Trade not found.'
|
||||
|
||||
Trade.query.session.rollback()
|
||||
Trade.rollback()
|
||||
create_mock_trades(fee, is_short=is_short)
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/trade/3")
|
||||
@@ -677,7 +668,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short):
|
||||
create_mock_trades(fee, is_short=is_short)
|
||||
|
||||
ftbot.strategy.order_types['stoploss_on_exchange'] = True
|
||||
trades = Trade.query.all()
|
||||
trades = Trade.session.scalars(select(Trade)).all()
|
||||
trades[1].stoploss_order_id = '1234'
|
||||
Trade.commit()
|
||||
assert len(trades) > 2
|
||||
@@ -685,7 +676,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short):
|
||||
rc = client_delete(client, f"{BASE_URI}/trades/1")
|
||||
assert_response(rc)
|
||||
assert rc.json()['result_msg'] == 'Deleted trade 1. Closed 1 open orders.'
|
||||
assert len(trades) - 1 == len(Trade.query.all())
|
||||
assert len(trades) - 1 == len(Trade.session.scalars(select(Trade)).all())
|
||||
assert cancel_mock.call_count == 1
|
||||
|
||||
cancel_mock.reset_mock()
|
||||
@@ -694,11 +685,11 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short):
|
||||
assert_response(rc, 502)
|
||||
assert cancel_mock.call_count == 0
|
||||
|
||||
assert len(trades) - 1 == len(Trade.query.all())
|
||||
assert len(trades) - 1 == len(Trade.session.scalars(select(Trade)).all())
|
||||
rc = client_delete(client, f"{BASE_URI}/trades/2")
|
||||
assert_response(rc)
|
||||
assert rc.json()['result_msg'] == 'Deleted trade 2. Closed 2 open orders.'
|
||||
assert len(trades) - 2 == len(Trade.query.all())
|
||||
assert len(trades) - 2 == len(Trade.session.scalars(select(Trade)).all())
|
||||
assert stoploss_mock.call_count == 1
|
||||
|
||||
rc = client_delete(client, f"{BASE_URI}/trades/502")
|
||||
@@ -943,7 +934,7 @@ def test_api_performance(botclient, fee):
|
||||
)
|
||||
trade.close_profit = trade.calc_profit_ratio(trade.close_rate)
|
||||
trade.close_profit_abs = trade.calc_profit(trade.close_rate)
|
||||
Trade.query.session.add(trade)
|
||||
Trade.session.add(trade)
|
||||
|
||||
trade = Trade(
|
||||
pair='XRP/ETH',
|
||||
@@ -960,7 +951,7 @@ def test_api_performance(botclient, fee):
|
||||
trade.close_profit = trade.calc_profit_ratio(trade.close_rate)
|
||||
trade.close_profit_abs = trade.calc_profit(trade.close_rate)
|
||||
|
||||
Trade.query.session.add(trade)
|
||||
Trade.session.add(trade)
|
||||
Trade.commit()
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/performance")
|
||||
@@ -1012,6 +1003,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
|
||||
'profit_fiat': ANY,
|
||||
'total_profit_abs': ANY,
|
||||
'total_profit_fiat': ANY,
|
||||
'total_profit_ratio': ANY,
|
||||
'realized_profit': 0.0,
|
||||
'realized_profit_ratio': None,
|
||||
'current_rate': current_rate,
|
||||
@@ -1064,6 +1056,9 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
|
||||
'liquidation_price': None,
|
||||
'funding_fees': None,
|
||||
'trading_mode': ANY,
|
||||
'amount_precision': None,
|
||||
'price_precision': None,
|
||||
'precision_mode': None,
|
||||
'orders': [ANY],
|
||||
}
|
||||
|
||||
@@ -1269,6 +1264,9 @@ def test_api_force_entry(botclient, mocker, fee, endpoint):
|
||||
'liquidation_price': None,
|
||||
'funding_fees': None,
|
||||
'trading_mode': 'spot',
|
||||
'amount_precision': None,
|
||||
'price_precision': None,
|
||||
'precision_mode': None,
|
||||
'orders': [],
|
||||
}
|
||||
|
||||
@@ -1289,7 +1287,7 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets):
|
||||
data={"tradeid": "1"})
|
||||
assert_response(rc, 502)
|
||||
assert rc.json() == {"error": "Error querying /api/v1/forceexit: invalid argument"}
|
||||
Trade.query.session.rollback()
|
||||
Trade.rollback()
|
||||
|
||||
create_mock_trades(fee)
|
||||
trade = Trade.get_trades([Trade.id == 5]).first()
|
||||
@@ -1298,7 +1296,7 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets):
|
||||
data={"tradeid": "5", "ordertype": "market", "amount": 23})
|
||||
assert_response(rc)
|
||||
assert rc.json() == {'result': 'Created sell order for trade 5.'}
|
||||
Trade.query.session.rollback()
|
||||
Trade.rollback()
|
||||
|
||||
trade = Trade.get_trades([Trade.id == 5]).first()
|
||||
assert pytest.approx(trade.amount) == 100
|
||||
@@ -1308,7 +1306,7 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets):
|
||||
data={"tradeid": "5"})
|
||||
assert_response(rc)
|
||||
assert rc.json() == {'result': 'Created sell order for trade 5.'}
|
||||
Trade.query.session.rollback()
|
||||
Trade.rollback()
|
||||
|
||||
trade = Trade.get_trades([Trade.id == 5]).first()
|
||||
assert trade.is_open is False
|
||||
|
@@ -14,6 +14,7 @@ import arrow
|
||||
import pytest
|
||||
import time_machine
|
||||
from pandas import DataFrame
|
||||
from sqlalchemy import select
|
||||
from telegram import Chat, Message, ReplyKeyboardMarkup, Update
|
||||
from telegram.error import BadRequest, NetworkError, TelegramError
|
||||
|
||||
@@ -198,6 +199,7 @@ def test_telegram_status(default_conf, update, mocker) -> None:
|
||||
'current_rate': 1.098e-05,
|
||||
'amount': 90.99181074,
|
||||
'stake_amount': 90.99181074,
|
||||
'max_stake_amount': 90.99181074,
|
||||
'buy_tag': None,
|
||||
'enter_tag': None,
|
||||
'close_profit_ratio': None,
|
||||
@@ -301,8 +303,7 @@ def test_telegram_status_closed_trade(default_conf, update, mocker, fee) -> None
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
create_mock_trades(fee)
|
||||
trades = Trade.get_trades([Trade.is_open.is_(False)])
|
||||
trade = trades[0]
|
||||
trade = Trade.get_trades([Trade.is_open.is_(False)]).first()
|
||||
context = MagicMock()
|
||||
context.args = [str(trade.id)]
|
||||
telegram._status(update=update, context=context)
|
||||
@@ -651,7 +652,7 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac
|
||||
|
||||
# The one-digit months should contain a zero, Eg: September 2021 = "2021-09"
|
||||
# Since we loaded the last 12 months, any month should appear
|
||||
assert str('-09') in msg_mock.call_args_list[0][0][0]
|
||||
assert '-09' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
# Try invalid data
|
||||
msg_mock.reset_mock()
|
||||
@@ -670,11 +671,12 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac
|
||||
context = MagicMock()
|
||||
context.args = ["february"]
|
||||
telegram._monthly(update=update, context=context)
|
||||
assert str('Monthly Profit over the last 6 months</b>:') in msg_mock.call_args_list[0][0][0]
|
||||
assert 'Monthly Profit over the last 6 months</b>:' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, fee,
|
||||
limit_sell_order_usdt, mocker) -> None:
|
||||
def test_telegram_profit_handle(
|
||||
default_conf_usdt, update, ticker_usdt, ticker_sell_up, fee,
|
||||
limit_sell_order_usdt, mocker) -> None:
|
||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=1.1)
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
@@ -692,7 +694,7 @@ def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, f
|
||||
|
||||
# Create some test data
|
||||
freqtradebot.enter_positions()
|
||||
trade = Trade.query.first()
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
|
||||
context = MagicMock()
|
||||
# Test with invalid 2nd argument (should silently pass)
|
||||
@@ -709,6 +711,7 @@ def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, f
|
||||
# Update the ticker with a market going up
|
||||
mocker.patch(f'{EXMS}.fetch_ticker', ticker_sell_up)
|
||||
# Simulate fulfilled LIMIT_SELL order for trade
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
oobj = Order.parse_from_ccxt_object(
|
||||
limit_sell_order_usdt, limit_sell_order_usdt['symbol'], 'sell')
|
||||
trade.orders.append(oobj)
|
||||
@@ -945,7 +948,7 @@ def test_telegram_forceexit_handle(default_conf, update, ticker, fee,
|
||||
# Create some test data
|
||||
freqtradebot.enter_positions()
|
||||
|
||||
trade = Trade.query.first()
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade
|
||||
|
||||
# Increase the price and sell it
|
||||
@@ -1020,7 +1023,7 @@ def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee,
|
||||
fetch_ticker=ticker_sell_down
|
||||
)
|
||||
|
||||
trade = Trade.query.first()
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade
|
||||
|
||||
# /forceexit 1
|
||||
@@ -1729,14 +1732,14 @@ def test_version_handle(default_conf, update, mocker) -> None:
|
||||
|
||||
telegram._version(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count == 1
|
||||
assert '*Version:* `{}`'.format(__version__) in msg_mock.call_args_list[0][0][0]
|
||||
assert f'*Version:* `{__version__}`' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
msg_mock.reset_mock()
|
||||
freqtradebot.strategy.version = lambda: '1.1.1'
|
||||
|
||||
telegram._version(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count == 1
|
||||
assert '*Version:* `{}`'.format(__version__) in msg_mock.call_args_list[0][0][0]
|
||||
assert f'*Version:* `{__version__}`' in msg_mock.call_args_list[0][0][0]
|
||||
assert '*Strategy version: * `1.1.1`' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
|
@@ -50,6 +50,7 @@ class HyperoptableStrategy(StrategyTestV3):
|
||||
return prot
|
||||
|
||||
bot_loop_started = False
|
||||
bot_started = False
|
||||
|
||||
def bot_loop_start(self):
|
||||
self.bot_loop_started = True
|
||||
@@ -58,6 +59,7 @@ class HyperoptableStrategy(StrategyTestV3):
|
||||
"""
|
||||
Parameters can also be defined here ...
|
||||
"""
|
||||
self.bot_started = True
|
||||
self.buy_rsi = IntParameter([0, 50], default=30, space='buy')
|
||||
|
||||
def informative_pairs(self):
|
||||
|
@@ -986,7 +986,8 @@ def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog):
|
||||
}
|
||||
}
|
||||
}
|
||||
mocker.patch('freqtrade.strategy.hyper.json_load', return_value=expected_result)
|
||||
mocker.patch('freqtrade.strategy.hyper.HyperoptTools.load_params',
|
||||
return_value=expected_result)
|
||||
PairLocks.timeframe = default_conf['timeframe']
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
assert strategy.stoploss == -0.05
|
||||
@@ -1005,11 +1006,13 @@ def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog):
|
||||
}
|
||||
}
|
||||
|
||||
mocker.patch('freqtrade.strategy.hyper.json_load', return_value=expected_result)
|
||||
mocker.patch('freqtrade.strategy.hyper.HyperoptTools.load_params',
|
||||
return_value=expected_result)
|
||||
with pytest.raises(OperationalException, match="Invalid parameter file provided."):
|
||||
StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
mocker.patch('freqtrade.strategy.hyper.json_load', MagicMock(side_effect=ValueError()))
|
||||
mocker.patch('freqtrade.strategy.hyper.HyperoptTools.load_params',
|
||||
MagicMock(side_effect=ValueError()))
|
||||
|
||||
StrategyResolver.load_strategy(default_conf)
|
||||
assert log_has("Invalid parameter file format.", caplog)
|
||||
|
@@ -177,26 +177,30 @@ def test_stoploss_from_open(side, profitrange):
|
||||
("long", 0.1, 0.2, 1, 0.08333333),
|
||||
("long", 0.1, 0.5, 1, 0.266666666),
|
||||
("long", 0.1, 5, 1, 0.816666666), # 500% profit, set stoploss to 10% above open price
|
||||
("long", 0, 5, 10, 3.3333333), # 500% profit, set stoploss break even
|
||||
("long", 0.1, 5, 10, 3.26666666), # 500% profit, set stoploss to 10% above open price
|
||||
("long", -0.1, 5, 10, 3.3999999), # 500% profit, set stoploss to 10% belowopen price
|
||||
|
||||
("short", 0, 0.1, 1, 0.1111111),
|
||||
("short", -0.1, 0.1, 1, 0.2222222),
|
||||
("short", 0.1, 0.2, 1, 0.125),
|
||||
("short", 0.1, 1, 1, 1),
|
||||
("short", -0.01, 5, 10, 10.01999999), # 500% profit at 10x
|
||||
])
|
||||
def test_stoploss_from_open_leverage(side, rel_stop, curr_profit, leverage, expected):
|
||||
|
||||
stoploss = stoploss_from_open(rel_stop, curr_profit, side == 'short')
|
||||
stoploss = stoploss_from_open(rel_stop, curr_profit, side == 'short', leverage)
|
||||
assert pytest.approx(stoploss) == expected
|
||||
open_rate = 100
|
||||
if stoploss != 1:
|
||||
if side == 'long':
|
||||
current_rate = open_rate * (1 + curr_profit)
|
||||
stop = current_rate * (1 - stoploss)
|
||||
assert pytest.approx(stop) == open_rate * (1 + rel_stop)
|
||||
current_rate = open_rate * (1 + curr_profit / leverage)
|
||||
stop = current_rate * (1 - stoploss / leverage)
|
||||
assert pytest.approx(stop) == open_rate * (1 + rel_stop / leverage)
|
||||
else:
|
||||
current_rate = open_rate * (1 - curr_profit)
|
||||
stop = current_rate * (1 + stoploss)
|
||||
assert pytest.approx(stop) == open_rate * (1 - rel_stop)
|
||||
current_rate = open_rate * (1 - curr_profit / leverage)
|
||||
stop = current_rate * (1 + stoploss / leverage)
|
||||
assert pytest.approx(stop) == open_rate * (1 - rel_stop / leverage)
|
||||
|
||||
|
||||
def test_stoploss_from_absolute():
|
||||
|
@@ -69,7 +69,7 @@ def test_load_strategy(default_conf, dataframe_1m):
|
||||
def test_load_strategy_base64(dataframe_1m, caplog, default_conf):
|
||||
filepath = Path(__file__).parents[2] / 'freqtrade/templates/sample_strategy.py'
|
||||
encoded_string = urlsafe_b64encode(filepath.read_bytes()).decode("utf-8")
|
||||
default_conf.update({'strategy': 'SampleStrategy:{}'.format(encoded_string)})
|
||||
default_conf.update({'strategy': f'SampleStrategy:{encoded_string}'})
|
||||
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
assert 'rsi' in strategy.advise_indicators(dataframe_1m, {'pair': 'ETH/BTC'})
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import select
|
||||
|
||||
from freqtrade.enums import ExitCheckTuple, ExitType, TradingMode
|
||||
from freqtrade.persistence import Trade
|
||||
@@ -34,7 +35,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
"type": "stop_loss_limit",
|
||||
"side": "sell",
|
||||
"price": 1.08801,
|
||||
"amount": 90.99181074,
|
||||
"amount": 91.07468123,
|
||||
"cost": 0.0,
|
||||
"average": 0.0,
|
||||
"filled": 0.0,
|
||||
@@ -48,17 +49,18 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
stoploss_order_closed['filled'] = stoploss_order_closed['amount']
|
||||
|
||||
# Sell first trade based on stoploss, keep 2nd and 3rd trade open
|
||||
stop_orders = [stoploss_order_closed, stoploss_order_open, stoploss_order_open]
|
||||
stoploss_order_mock = MagicMock(
|
||||
side_effect=[stoploss_order_closed, stoploss_order_open, stoploss_order_open])
|
||||
side_effect=stop_orders)
|
||||
# Sell 3rd trade (not called for the first trade)
|
||||
should_sell_mock = MagicMock(side_effect=[
|
||||
[],
|
||||
[ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)]]
|
||||
)
|
||||
cancel_order_mock = MagicMock()
|
||||
mocker.patch('freqtrade.exchange.binance.Binance.create_stoploss', stoploss)
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
create_stoploss=stoploss,
|
||||
fetch_ticker=ticker,
|
||||
get_fee=fee,
|
||||
amount_to_precision=lambda s, x, y: y,
|
||||
@@ -91,14 +93,15 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
assert freqtrade.strategy.confirm_trade_exit.call_count == 0
|
||||
wallets_mock.reset_mock()
|
||||
|
||||
trades = Trade.query.all()
|
||||
# Make sure stoploss-order is open and trade is bought (since we mock update_trade_state)
|
||||
for trade in trades:
|
||||
stoploss_order_closed['id'] = '3'
|
||||
oobj = Order.parse_from_ccxt_object(stoploss_order_closed, trade.pair, 'stoploss')
|
||||
trades = Trade.session.scalars(select(Trade)).all()
|
||||
# Make sure stoploss-order is open and trade is bought
|
||||
for idx, trade in enumerate(trades):
|
||||
stop_order = stop_orders[idx]
|
||||
stop_order['id'] = f"stop{idx}"
|
||||
oobj = Order.parse_from_ccxt_object(stop_order, trade.pair, 'stoploss')
|
||||
|
||||
trade.orders.append(oobj)
|
||||
trade.stoploss_order_id = '3'
|
||||
trade.stoploss_order_id = f"stop{idx}"
|
||||
trade.open_order_id = None
|
||||
|
||||
n = freqtrade.exit_positions(trades)
|
||||
@@ -179,13 +182,13 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati
|
||||
n = freqtrade.enter_positions()
|
||||
assert n == 4
|
||||
|
||||
trades = Trade.query.all()
|
||||
trades = Trade.session.scalars(select(Trade)).all()
|
||||
assert len(trades) == 4
|
||||
assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC') == result1
|
||||
|
||||
rpc._rpc_force_entry('TKN/BTC', None)
|
||||
|
||||
trades = Trade.query.all()
|
||||
trades = Trade.session.scalars(select(Trade)).all()
|
||||
assert len(trades) == 5
|
||||
|
||||
for trade in trades:
|
||||
@@ -385,12 +388,12 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
assert trade.open_order_id is not None
|
||||
assert pytest.approx(trade.stake_amount) == 60
|
||||
assert trade.open_rate == 1.96
|
||||
assert trade.stop_loss_pct is None
|
||||
assert trade.stop_loss == 0.0
|
||||
assert trade.stop_loss_pct == -0.1
|
||||
assert pytest.approx(trade.stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
|
||||
assert pytest.approx(trade.initial_stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
|
||||
assert trade.initial_stop_loss_pct == -0.1
|
||||
assert trade.leverage == leverage
|
||||
assert trade.stake_amount == 60
|
||||
assert trade.initial_stop_loss == 0.0
|
||||
assert trade.initial_stop_loss_pct is None
|
||||
# No adjustment
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
@@ -406,11 +409,11 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
assert trade.open_order_id is not None
|
||||
# Open rate is not adjusted yet
|
||||
assert trade.open_rate == 1.96
|
||||
assert trade.stop_loss_pct is None
|
||||
assert trade.stop_loss == 0.0
|
||||
assert trade.stop_loss_pct == -0.1
|
||||
assert pytest.approx(trade.stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
|
||||
assert pytest.approx(trade.initial_stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
|
||||
assert trade.stake_amount == 60
|
||||
assert trade.initial_stop_loss == 0.0
|
||||
assert trade.initial_stop_loss_pct is None
|
||||
assert trade.initial_stop_loss_pct == -0.1
|
||||
|
||||
# Fill order
|
||||
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True)
|
||||
@@ -423,7 +426,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
assert pytest.approx(trade.stake_amount) == 60
|
||||
assert trade.stop_loss_pct == -0.1
|
||||
assert pytest.approx(trade.stop_loss) == 1.99 * (1 - 0.1 / leverage)
|
||||
assert pytest.approx(trade.initial_stop_loss) == 1.99 * (1 - 0.1 / leverage)
|
||||
assert pytest.approx(trade.initial_stop_loss) == 1.96 * (1 - 0.1 / leverage)
|
||||
assert trade.initial_stop_loss_pct == -0.1
|
||||
|
||||
# 2nd order - not filling
|
||||
|
214
tests/test_strategy_updater.py
Normal file
214
tests/test_strategy_updater.py
Normal file
@@ -0,0 +1,214 @@
|
||||
# pragma pylint: disable=missing-docstring, protected-access, invalid-name
|
||||
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade.commands.strategy_utils_commands import start_strategy_update
|
||||
from freqtrade.strategy.strategyupdater import StrategyUpdater
|
||||
from tests.conftest import get_args
|
||||
|
||||
|
||||
if sys.version_info < (3, 9):
|
||||
pytest.skip("StrategyUpdater is not compatible with Python 3.8", allow_module_level=True)
|
||||
|
||||
|
||||
def test_strategy_updater_start(tmpdir, capsys) -> None:
|
||||
# Effective test without mocks.
|
||||
teststrats = Path(__file__).parent / 'strategy/strats'
|
||||
tmpdirp = Path(tmpdir) / "strategies"
|
||||
tmpdirp.mkdir()
|
||||
shutil.copy(teststrats / 'strategy_test_v2.py', tmpdirp)
|
||||
old_code = (teststrats / 'strategy_test_v2.py').read_text()
|
||||
|
||||
args = [
|
||||
"strategy-updater",
|
||||
"--userdir",
|
||||
str(tmpdir),
|
||||
"--strategy-list",
|
||||
"StrategyTestV2"
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
|
||||
start_strategy_update(pargs)
|
||||
|
||||
assert Path(tmpdir / "strategies_orig_updater").exists()
|
||||
# Backup file exists
|
||||
assert Path(tmpdir / "strategies_orig_updater" / 'strategy_test_v2.py').exists()
|
||||
# updated file exists
|
||||
new_file = Path(tmpdirp / 'strategy_test_v2.py')
|
||||
assert new_file.exists()
|
||||
new_code = new_file.read_text()
|
||||
assert 'INTERFACE_VERSION = 3' in new_code
|
||||
assert 'INTERFACE_VERSION = 2' in old_code
|
||||
captured = capsys.readouterr()
|
||||
|
||||
assert 'Conversion of strategy_test_v2.py started.' in captured.out
|
||||
assert re.search(r'Conversion of strategy_test_v2\.py took .* seconds', captured.out)
|
||||
|
||||
|
||||
def test_strategy_updater_methods(default_conf, caplog) -> None:
|
||||
|
||||
instance_strategy_updater = StrategyUpdater()
|
||||
modified_code1 = instance_strategy_updater.update_code("""
|
||||
class testClass(IStrategy):
|
||||
def populate_buy_trend():
|
||||
pass
|
||||
def populate_sell_trend():
|
||||
pass
|
||||
def check_buy_timeout():
|
||||
pass
|
||||
def check_sell_timeout():
|
||||
pass
|
||||
def custom_sell():
|
||||
pass
|
||||
""")
|
||||
|
||||
assert "populate_entry_trend" in modified_code1
|
||||
assert "populate_exit_trend" in modified_code1
|
||||
assert "check_entry_timeout" in modified_code1
|
||||
assert "check_exit_timeout" in modified_code1
|
||||
assert "custom_exit" in modified_code1
|
||||
assert "INTERFACE_VERSION = 3" in modified_code1
|
||||
|
||||
|
||||
def test_strategy_updater_params(default_conf, caplog) -> None:
|
||||
instance_strategy_updater = StrategyUpdater()
|
||||
|
||||
modified_code2 = instance_strategy_updater.update_code("""
|
||||
ticker_interval = '15m'
|
||||
buy_some_parameter = IntParameter(space='buy')
|
||||
sell_some_parameter = IntParameter(space='sell')
|
||||
""")
|
||||
|
||||
assert "timeframe" in modified_code2
|
||||
# check for not editing hyperopt spaces
|
||||
assert "space='buy'" in modified_code2
|
||||
assert "space='sell'" in modified_code2
|
||||
|
||||
|
||||
def test_strategy_updater_constants(default_conf, caplog) -> None:
|
||||
instance_strategy_updater = StrategyUpdater()
|
||||
modified_code3 = instance_strategy_updater.update_code("""
|
||||
use_sell_signal = True
|
||||
sell_profit_only = True
|
||||
sell_profit_offset = True
|
||||
ignore_roi_if_buy_signal = True
|
||||
forcebuy_enable = True
|
||||
""")
|
||||
|
||||
assert "use_exit_signal" in modified_code3
|
||||
assert "exit_profit_only" in modified_code3
|
||||
assert "exit_profit_offset" in modified_code3
|
||||
assert "ignore_roi_if_entry_signal" in modified_code3
|
||||
assert "force_entry_enable" in modified_code3
|
||||
|
||||
|
||||
def test_strategy_updater_df_columns(default_conf, caplog) -> None:
|
||||
instance_strategy_updater = StrategyUpdater()
|
||||
modified_code = instance_strategy_updater.update_code("""
|
||||
dataframe.loc[reduce(lambda x, y: x & y, conditions), ["buy", "buy_tag"]] = (1, "buy_signal_1")
|
||||
dataframe.loc[reduce(lambda x, y: x & y, conditions), 'sell'] = 1
|
||||
""")
|
||||
|
||||
assert "enter_long" in modified_code
|
||||
assert "exit_long" in modified_code
|
||||
assert "enter_tag" in modified_code
|
||||
|
||||
|
||||
def test_strategy_updater_method_params(default_conf, caplog) -> None:
|
||||
instance_strategy_updater = StrategyUpdater()
|
||||
modified_code = instance_strategy_updater.update_code("""
|
||||
def confirm_trade_exit(sell_reason: str):
|
||||
nr_orders = trade.nr_of_successful_buys
|
||||
pass
|
||||
""")
|
||||
assert "exit_reason" in modified_code
|
||||
assert "nr_orders = trade.nr_of_successful_entries" in modified_code
|
||||
|
||||
|
||||
def test_strategy_updater_dicts(default_conf, caplog) -> None:
|
||||
instance_strategy_updater = StrategyUpdater()
|
||||
modified_code = instance_strategy_updater.update_code("""
|
||||
order_time_in_force = {
|
||||
'buy': 'gtc',
|
||||
'sell': 'ioc'
|
||||
}
|
||||
order_types = {
|
||||
'buy': 'limit',
|
||||
'sell': 'market',
|
||||
'stoploss': 'market',
|
||||
'stoploss_on_exchange': False
|
||||
}
|
||||
unfilledtimeout = {
|
||||
'buy': 1,
|
||||
'sell': 2
|
||||
}
|
||||
""")
|
||||
|
||||
assert "'entry': 'gtc'" in modified_code
|
||||
assert "'exit': 'ioc'" in modified_code
|
||||
assert "'entry': 'limit'" in modified_code
|
||||
assert "'exit': 'market'" in modified_code
|
||||
assert "'entry': 1" in modified_code
|
||||
assert "'exit': 2" in modified_code
|
||||
|
||||
|
||||
def test_strategy_updater_comparisons(default_conf, caplog) -> None:
|
||||
instance_strategy_updater = StrategyUpdater()
|
||||
modified_code = instance_strategy_updater.update_code("""
|
||||
def confirm_trade_exit(sell_reason):
|
||||
if (sell_reason == 'stop_loss'):
|
||||
pass
|
||||
""")
|
||||
assert "exit_reason" in modified_code
|
||||
assert "exit_reason == 'stop_loss'" in modified_code
|
||||
|
||||
|
||||
def test_strategy_updater_strings(default_conf, caplog) -> None:
|
||||
instance_strategy_updater = StrategyUpdater()
|
||||
|
||||
modified_code = instance_strategy_updater.update_code("""
|
||||
sell_reason == 'sell_signal'
|
||||
sell_reason == 'force_sell'
|
||||
sell_reason == 'emergency_sell'
|
||||
""")
|
||||
|
||||
# those tests currently don't work, next in line.
|
||||
assert "exit_signal" in modified_code
|
||||
assert "exit_reason" in modified_code
|
||||
assert "force_exit" in modified_code
|
||||
assert "emergency_exit" in modified_code
|
||||
|
||||
|
||||
def test_strategy_updater_comments(default_conf, caplog) -> None:
|
||||
instance_strategy_updater = StrategyUpdater()
|
||||
modified_code = instance_strategy_updater.update_code("""
|
||||
# This is the 1st comment
|
||||
import talib.abstract as ta
|
||||
# This is the 2nd comment
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
|
||||
|
||||
class someStrategy(IStrategy):
|
||||
INTERFACE_VERSION = 2
|
||||
# This is the 3rd comment
|
||||
# This attribute will be overridden if the config file contains "minimal_roi"
|
||||
minimal_roi = {
|
||||
"0": 0.50
|
||||
}
|
||||
|
||||
# This is the 4th comment
|
||||
stoploss = -0.1
|
||||
""")
|
||||
|
||||
assert "This is the 1st comment" in modified_code
|
||||
assert "This is the 2nd comment" in modified_code
|
||||
assert "This is the 3rd comment" in modified_code
|
||||
assert "INTERFACE_VERSION = 3" in modified_code
|
||||
# currently still missing:
|
||||
# Webhook terminology, Telegram notification settings, Strategy/Config settings
|
BIN
tests/testdata/XRP_ETH-trades.feather
vendored
Normal file
BIN
tests/testdata/XRP_ETH-trades.feather
vendored
Normal file
Binary file not shown.
Reference in New Issue
Block a user