Merge branch 'develop' into pr/asuiu/8296
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)
|
||||
|
@@ -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', {'postOnly': True}),
|
||||
('sell', 'limit', 'PO', {'postOnly': True}),
|
||||
('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"),
|
||||
@@ -39,7 +52,7 @@ def test_create_stoploss_order_binance(default_conf, mocker, limitratio, expecte
|
||||
|
||||
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,
|
||||
@@ -118,7 +131,7 @@ def test_create_stoploss_order_dry_run_binance(default_conf, mocker):
|
||||
|
||||
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,
|
||||
},
|
||||
@@ -463,7 +463,9 @@ class TestCCXTExchange():
|
||||
if exchangename == 'gate':
|
||||
# TODO: Gate is unstable here at the moment, ignoring the limit partially.
|
||||
return
|
||||
for val in [1, 2, 5, 25, 100]:
|
||||
for val in [1, 2, 5, 25, 50, 100]:
|
||||
if val > 50 and exchangename == 'bybit':
|
||||
continue
|
||||
l2 = exch.fetch_l2_order_book(pair, val)
|
||||
if not l2_limit_range or val in l2_limit_range:
|
||||
if val > 50:
|
||||
|
@@ -13,8 +13,8 @@ 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,
|
||||
@@ -1052,9 +1052,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),
|
||||
@@ -1612,13 +1612,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,
|
||||
@@ -3400,7 +3400,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'
|
||||
|
||||
@@ -3881,29 +3881,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)
|
||||
|
@@ -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
|
||||
|
||||
@@ -31,7 +31,7 @@ def test_create_stoploss_order_huobi(default_conf, mocker, limitratio, expected,
|
||||
|
||||
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,
|
||||
@@ -84,7 +84,7 @@ def test_create_stoploss_order_dry_run_huobi(default_conf, mocker):
|
||||
|
||||
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)
|
||||
|
@@ -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
|
||||
|
||||
@@ -31,7 +31,7 @@ def test_create_stoploss_order_kucoin(default_conf, mocker, limitratio, expected
|
||||
|
||||
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,
|
||||
@@ -92,7 +92,7 @@ def test_stoploss_order_dry_run_kucoin(default_conf, mocker):
|
||||
|
||||
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)
|
||||
|
@@ -78,7 +78,9 @@ def make_rl_config(conf):
|
||||
"rr": 1,
|
||||
"profit_aim": 0.02,
|
||||
"win_reward_factor": 2
|
||||
}}
|
||||
},
|
||||
"drop_ohlc_from_features": False
|
||||
}
|
||||
|
||||
return conf
|
||||
|
||||
|
@@ -35,8 +35,8 @@ def test_freqai_backtest_start_backtest_list(freqai_conf, mocker, testdatadir, c
|
||||
args = get_args(args)
|
||||
bt_config = setup_optimize_configuration(args, RunMode.BACKTEST)
|
||||
Backtesting(bt_config)
|
||||
assert log_has_re('Using --strategy-list with FreqAI REQUIRES all strategies to have identical '
|
||||
'populate_any_indicators.', caplog)
|
||||
assert log_has_re('Using --strategy-list with FreqAI REQUIRES all strategies to have identical',
|
||||
caplog)
|
||||
Backtesting.cleanup()
|
||||
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import platform
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
@@ -17,6 +18,10 @@ from tests.conftest import EXMS, create_mock_trades, get_patched_exchange, log_h
|
||||
from tests.freqai.conftest import get_patched_freqai_strategy, make_rl_config
|
||||
|
||||
|
||||
def is_py11() -> bool:
|
||||
return sys.version_info >= (3, 11)
|
||||
|
||||
|
||||
def is_arm() -> bool:
|
||||
machine = platform.machine()
|
||||
return "arm" in machine or "aarch64" in machine
|
||||
@@ -27,6 +32,17 @@ def is_mac() -> bool:
|
||||
return "Darwin" in machine
|
||||
|
||||
|
||||
def can_run_model(model: str) -> None:
|
||||
if (is_arm() or is_py11()) and "Catboost" in model:
|
||||
pytest.skip("CatBoost is not supported on ARM")
|
||||
|
||||
if is_mac() and not is_arm() and 'Reinforcement' in model:
|
||||
pytest.skip("Reinforcement learning module not available on intel based Mac OS")
|
||||
|
||||
if is_py11() and 'Reinforcement' in model:
|
||||
pytest.skip("Reinforcement learning currently not available on python 3.11.")
|
||||
|
||||
|
||||
@pytest.mark.parametrize('model, pca, dbscan, float32, can_short, shuffle, buffer', [
|
||||
('LightGBMRegressor', True, False, True, True, False, 0),
|
||||
('XGBoostRegressor', False, True, False, True, False, 10),
|
||||
@@ -41,12 +57,7 @@ def is_mac() -> bool:
|
||||
def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
|
||||
dbscan, float32, can_short, shuffle, buffer):
|
||||
|
||||
if is_arm() and model == 'CatboostRegressor':
|
||||
pytest.skip("CatBoost is not supported on ARM")
|
||||
|
||||
if is_mac() and not is_arm() and 'Reinforcement' in model:
|
||||
pytest.skip("Reinforcement learning module not available on intel based Mac OS")
|
||||
|
||||
can_run_model(model)
|
||||
model_save_ext = 'joblib'
|
||||
freqai_conf.update({"freqaimodel": model})
|
||||
freqai_conf.update({"timerange": "20180110-20180130"})
|
||||
@@ -57,13 +68,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)
|
||||
@@ -73,6 +77,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
|
||||
|
||||
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
|
||||
exchange = get_patched_exchange(mocker, freqai_conf)
|
||||
@@ -117,7 +122,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
|
||||
('CatboostClassifierMultiTarget', "freqai_test_multimodel_classifier_strat")
|
||||
])
|
||||
def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, strat):
|
||||
if is_arm() and 'Catboost' in model:
|
||||
if (is_arm() or is_py11()) and 'Catboost' in model:
|
||||
pytest.skip("CatBoost is not supported on ARM")
|
||||
|
||||
freqai_conf.update({"timerange": "20180110-20180130"})
|
||||
@@ -159,7 +164,7 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, s
|
||||
'XGBoostRFClassifier',
|
||||
])
|
||||
def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
|
||||
if is_arm() and model == 'CatboostClassifier':
|
||||
if (is_arm() or is_py11()) and model == 'CatboostClassifier':
|
||||
pytest.skip("CatBoost is not supported on ARM")
|
||||
|
||||
freqai_conf.update({"freqaimodel": model})
|
||||
@@ -206,13 +211,11 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
|
||||
],
|
||||
)
|
||||
def test_start_backtesting(mocker, freqai_conf, model, num_files, strat, caplog):
|
||||
can_run_model(model)
|
||||
|
||||
freqai_conf.get("freqai", {}).update({"save_backtest_models": True})
|
||||
freqai_conf['runmode'] = RunMode.BACKTEST
|
||||
if is_arm() and "Catboost" in model:
|
||||
pytest.skip("CatBoost is not supported on ARM")
|
||||
|
||||
if is_mac() and 'Reinforcement' in model:
|
||||
pytest.skip("Reinforcement learning module not available on intel based Mac OS")
|
||||
Trade.use_db = False
|
||||
|
||||
freqai_conf.update({"freqaimodel": model})
|
||||
@@ -509,6 +512,8 @@ def test_get_state_info(mocker, freqai_conf, dp_exists, caplog, tickers):
|
||||
|
||||
if is_mac():
|
||||
pytest.skip("Reinforcement learning module not available on intel based Mac OS")
|
||||
if is_py11():
|
||||
pytest.skip("Reinforcement learning currently not available on python 3.11.")
|
||||
|
||||
freqai_conf.update({"freqaimodel": "ReinforcementLearner"})
|
||||
freqai_conf.update({"timerange": "20180110-20180130"})
|
||||
|
@@ -924,7 +924,7 @@ 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)
|
||||
patch_exchange(mocker)
|
||||
frame = _build_backtest_dataframe(data.data)
|
||||
backtesting = Backtesting(default_conf)
|
||||
|
@@ -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,70 +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,
|
||||
'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(
|
||||
@@ -1408,69 +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,
|
||||
'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):
|
||||
@@ -1492,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
|
||||
@@ -1554,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
|
||||
@@ -1616,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)
|
||||
@@ -1791,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 != ()
|
||||
@@ -2014,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',
|
||||
@@ -2440,8 +2458,9 @@ def test_select_filled_orders(fee):
|
||||
def test_order_to_ccxt(limit_buy_order_open):
|
||||
|
||||
order = Order.parse_from_ccxt_object(limit_buy_order_open, 'mocked', 'buy')
|
||||
order.query.session.add(order)
|
||||
Order.query.session.commit()
|
||||
order.ft_trade_id = 1
|
||||
order.session.add(order)
|
||||
Order.session.commit()
|
||||
|
||||
order_resp = Order.order_by_id(limit_buy_order_open['id'])
|
||||
assert order_resp
|
||||
@@ -2543,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]
|
||||
@@ -2572,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:
|
||||
@@ -2593,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,
|
||||
@@ -76,8 +77,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'stoploss_entry_dist_ratio': -0.10376381,
|
||||
'open_order': None,
|
||||
'realized_profit': 0.0,
|
||||
'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,
|
||||
@@ -85,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',
|
||||
@@ -122,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({
|
||||
@@ -167,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',
|
||||
@@ -180,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
|
||||
@@ -346,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
|
||||
@@ -709,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(
|
||||
@@ -745,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(
|
||||
@@ -763,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(
|
||||
|
@@ -14,6 +14,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
|
||||
@@ -624,7 +625,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 +653,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 +678,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 +686,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 +695,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 +944,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 +961,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,7 +1013,9 @@ 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,
|
||||
'open_date': ANY,
|
||||
'open_timestamp': ANY,
|
||||
@@ -1063,6 +1066,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],
|
||||
}
|
||||
|
||||
@@ -1179,7 +1185,7 @@ def test_api_force_entry(botclient, mocker, fee, endpoint):
|
||||
ftbot.config['force_entry_enable'] = True
|
||||
|
||||
fbuy_mock = MagicMock(return_value=None)
|
||||
mocker.patch("freqtrade.rpc.RPC._rpc_force_entry", fbuy_mock)
|
||||
mocker.patch("freqtrade.rpc.rpc.RPC._rpc_force_entry", fbuy_mock)
|
||||
rc = client_post(client, f"{BASE_URI}/{endpoint}",
|
||||
data={"pair": "ETH/BTC"})
|
||||
assert_response(rc)
|
||||
@@ -1205,7 +1211,7 @@ def test_api_force_entry(botclient, mocker, fee, endpoint):
|
||||
strategy=CURRENT_TEST_STRATEGY,
|
||||
trading_mode=TradingMode.SPOT
|
||||
))
|
||||
mocker.patch("freqtrade.rpc.RPC._rpc_force_entry", fbuy_mock)
|
||||
mocker.patch("freqtrade.rpc.rpc.RPC._rpc_force_entry", fbuy_mock)
|
||||
|
||||
rc = client_post(client, f"{BASE_URI}/{endpoint}",
|
||||
data={"pair": "ETH/BTC"})
|
||||
@@ -1243,6 +1249,7 @@ def test_api_force_entry(botclient, mocker, fee, endpoint):
|
||||
'profit_abs': None,
|
||||
'profit_fiat': None,
|
||||
'realized_profit': 0.0,
|
||||
'realized_profit_ratio': None,
|
||||
'fee_close': 0.0025,
|
||||
'fee_close_cost': None,
|
||||
'fee_close_currency': None,
|
||||
@@ -1267,6 +1274,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': [],
|
||||
}
|
||||
|
||||
@@ -1287,7 +1297,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()
|
||||
@@ -1296,7 +1306,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
|
||||
@@ -1306,7 +1316,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,
|
||||
@@ -279,6 +281,7 @@ def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None:
|
||||
assert msg_mock.call_count == 4
|
||||
msg = msg_mock.call_args_list[0][0][0]
|
||||
assert re.search(r'Number of Entries.*2', msg)
|
||||
assert re.search(r'Number of Exits.*0', msg)
|
||||
assert re.search(r'Average Entry Price', msg)
|
||||
assert re.search(r'Order filled', msg)
|
||||
assert re.search(r'Close Date:', msg) is None
|
||||
@@ -300,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)
|
||||
@@ -650,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()
|
||||
@@ -669,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,
|
||||
@@ -691,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)
|
||||
@@ -708,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)
|
||||
@@ -944,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
|
||||
@@ -1019,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
|
||||
@@ -1209,7 +1213,7 @@ def test_force_enter_handle(default_conf, update, mocker) -> None:
|
||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||
|
||||
fbuy_mock = MagicMock(return_value=None)
|
||||
mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock)
|
||||
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_force_entry', fbuy_mock)
|
||||
|
||||
telegram, freqtradebot, _ = get_telegram_testobject(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot)
|
||||
@@ -1226,7 +1230,7 @@ def test_force_enter_handle(default_conf, update, mocker) -> None:
|
||||
|
||||
# Reset and retry with specified price
|
||||
fbuy_mock = MagicMock(return_value=None)
|
||||
mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock)
|
||||
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_force_entry', fbuy_mock)
|
||||
# /forcelong ETH/BTC 0.055
|
||||
context = MagicMock()
|
||||
context.args = ["ETH/BTC", "0.055"]
|
||||
@@ -1255,7 +1259,7 @@ def test_force_enter_no_pair(default_conf, update, mocker) -> None:
|
||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||
|
||||
fbuy_mock = MagicMock(return_value=None)
|
||||
mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock)
|
||||
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_force_entry', fbuy_mock)
|
||||
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
@@ -1728,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]
|
||||
|
||||
|
||||
@@ -2011,7 +2015,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
'sub_trade': True,
|
||||
})
|
||||
assert msg_mock.call_args[0][0] == (
|
||||
'\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
|
||||
'\N{WARNING SIGN} *Binance (dry):* Partially exiting KEY/ETH (#1)\n'
|
||||
'*Unrealized Sub Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
|
||||
'*Cumulative Profit:* (`-0.15746268 ETH / -24.812 USD`)\n'
|
||||
'*Enter Tag:* `buy_signal1`\n'
|
||||
|
@@ -7,6 +7,7 @@ from datetime import datetime
|
||||
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.persistence.trade_model import Order
|
||||
from freqtrade.strategy.interface import IStrategy
|
||||
|
||||
|
||||
@@ -35,7 +36,7 @@ class TestStrategyImplementBuyTimeout(TestStrategyNoImplementSell):
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
return super().populate_exit_trend(dataframe, metadata)
|
||||
|
||||
def check_buy_timeout(self, pair: str, trade, order: dict,
|
||||
def check_buy_timeout(self, pair: str, trade, order: Order,
|
||||
current_time: datetime, **kwargs) -> bool:
|
||||
return False
|
||||
|
||||
@@ -44,6 +45,6 @@ class TestStrategyImplementSellTimeout(TestStrategyNoImplementSell):
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
return super().populate_exit_trend(dataframe, metadata)
|
||||
|
||||
def check_sell_timeout(self, pair: str, trade, order: dict,
|
||||
def check_sell_timeout(self, pair: str, trade, order: Order,
|
||||
current_time: datetime, **kwargs) -> bool:
|
||||
return False
|
||||
|
@@ -197,7 +197,7 @@ class StrategyTestV3(IStrategy):
|
||||
|
||||
if current_profit < -0.0075:
|
||||
orders = trade.select_filled_orders(trade.entry_side)
|
||||
return round(orders[0].cost, 0)
|
||||
return round(orders[0].safe_cost, 0)
|
||||
|
||||
return None
|
||||
|
||||
|
@@ -291,18 +291,6 @@ def test_advise_all_indicators(default_conf, testdatadir) -> None:
|
||||
assert len(processed['UNITTEST/BTC']) == 103
|
||||
|
||||
|
||||
def test_populate_any_indicators(default_conf, testdatadir) -> None:
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
||||
data = load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange,
|
||||
fill_up_missing=True)
|
||||
processed = strategy.populate_any_indicators('UNITTEST/BTC', data, '5m')
|
||||
assert processed == data
|
||||
assert id(processed) == id(data)
|
||||
assert len(processed['UNITTEST/BTC']) == 103
|
||||
|
||||
|
||||
def test_freqai_not_initialized(default_conf) -> None:
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
strategy.ft_bot_start()
|
||||
|
@@ -169,6 +169,40 @@ def test_stoploss_from_open(side, profitrange):
|
||||
assert pytest.approx(stop_price) == expected_stop_price
|
||||
|
||||
|
||||
@pytest.mark.parametrize("side,rel_stop,curr_profit,leverage,expected", [
|
||||
# profit range for long is [-1, inf] while for shorts is [-inf, 1]
|
||||
("long", 0, -1, 1, 1),
|
||||
("long", 0, 0.1, 1, 0.09090909),
|
||||
("long", -0.1, 0.1, 1, 0.18181818),
|
||||
("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', leverage)
|
||||
assert pytest.approx(stoploss) == expected
|
||||
open_rate = 100
|
||||
if stoploss != 1:
|
||||
if side == 'long':
|
||||
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 / leverage)
|
||||
stop = current_rate * (1 + stoploss / leverage)
|
||||
assert pytest.approx(stop) == open_rate * (1 - rel_stop / leverage)
|
||||
|
||||
|
||||
def test_stoploss_from_absolute():
|
||||
assert pytest.approx(stoploss_from_absolute(90, 100)) == 1 - (90 / 100)
|
||||
assert pytest.approx(stoploss_from_absolute(90, 100)) == 0.1
|
||||
|
@@ -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
|
||||
@@ -56,9 +57,9 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
[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,7 +92,7 @@ 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()
|
||||
trades = Trade.session.scalars(select(Trade)).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'
|
||||
@@ -179,13 +180,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 +386,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 +407,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 +424,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
|
Reference in New Issue
Block a user