Merge branch 'develop' into pr/mkavinkumar1/6545

This commit is contained in:
Matthias
2022-05-26 19:51:36 +02:00
67 changed files with 9040 additions and 8305 deletions

View File

@@ -1495,7 +1495,7 @@ def test_start_convert_db(mocker, fee, tmpdir, caplog):
]
assert not db_src_file.is_file()
init_db(db_from, False)
init_db(db_from)
create_mock_trades(fee)

View File

@@ -384,7 +384,7 @@ def patch_coingekko(mocker) -> None:
@pytest.fixture(scope='function')
def init_persistence(default_conf):
init_db(default_conf['db_url'], default_conf['dry_run'])
init_db(default_conf['db_url'])
@pytest.fixture(scope="function")

View File

@@ -490,11 +490,11 @@ def test_fill_leverage_tiers_binance_dryrun(default_conf, mocker, leverage_tiers
default_conf['margin_mode'] = MarginMode.ISOLATED
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance")
exchange.fill_leverage_tiers()
leverage_tiers = leverage_tiers
assert len(exchange._leverage_tiers.keys()) > 100
for key, value in leverage_tiers.items():
assert exchange._leverage_tiers[key] == value
v = exchange._leverage_tiers[key]
assert isinstance(v, list)
assert len(v) == len(value)
def test__set_leverage_binance(mocker, default_conf):

View File

@@ -2206,6 +2206,8 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
@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()
caplog.set_level(logging.INFO)
api_mock = MagicMock()
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.DDoSProtection(
@@ -2883,6 +2885,7 @@ def test_get_historic_trades_notsupported(default_conf, mocker, caplog, exchange
until=trades_history[-1][0])
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_cancel_order_dry_run(default_conf, mocker, exchange_name):
default_conf['dry_run'] = True
@@ -3048,6 +3051,7 @@ def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name):
exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=123)
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_fetch_order(default_conf, mocker, exchange_name, caplog):
default_conf['dry_run'] = True
@@ -3100,6 +3104,7 @@ def test_fetch_order(default_conf, mocker, exchange_name, caplog):
order_id='_', pair='TKN/BTC')
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
# Don't test FTX here - that needs a separate test
@@ -3987,6 +3992,70 @@ def test_calculate_funding_fees(
) == kraken_fee
@pytest.mark.parametrize(
'mark_price,funding_rate,futures_funding_rate', [
(1000, 0.001, None),
(1000, 0.001, 0.01),
(1000, 0.001, 0.0),
(1000, 0.001, -0.01),
])
def test_combine_funding_and_mark(
default_conf,
mocker,
funding_rate,
mark_price,
futures_funding_rate,
):
exchange = get_patched_exchange(mocker, default_conf)
prior2_date = timeframe_to_prev_date('1h', datetime.now(timezone.utc) - timedelta(hours=2))
prior_date = timeframe_to_prev_date('1h', datetime.now(timezone.utc) - timedelta(hours=1))
trade_date = timeframe_to_prev_date('1h', datetime.now(timezone.utc))
funding_rates = DataFrame([
{'date': prior2_date, 'open': funding_rate},
{'date': prior_date, 'open': funding_rate},
{'date': trade_date, 'open': funding_rate},
])
mark_rates = DataFrame([
{'date': prior2_date, 'open': mark_price},
{'date': prior_date, 'open': mark_price},
{'date': trade_date, 'open': mark_price},
])
df = exchange.combine_funding_and_mark(funding_rates, mark_rates, futures_funding_rate)
assert 'open_mark' in df.columns
assert 'open_fund' in df.columns
assert len(df) == 3
funding_rates = DataFrame([
{'date': trade_date, 'open': funding_rate},
])
mark_rates = DataFrame([
{'date': prior2_date, 'open': mark_price},
{'date': prior_date, 'open': mark_price},
{'date': trade_date, 'open': mark_price},
])
df = exchange.combine_funding_and_mark(funding_rates, mark_rates, futures_funding_rate)
if futures_funding_rate is not None:
assert len(df) == 3
assert df.iloc[0]['open_fund'] == futures_funding_rate
assert df.iloc[1]['open_fund'] == futures_funding_rate
assert df.iloc[2]['open_fund'] == funding_rate
else:
assert len(df) == 1
# Empty funding rates
funding_rates = DataFrame([], columns=['date', 'open'])
df = exchange.combine_funding_and_mark(funding_rates, mark_rates, futures_funding_rate)
if futures_funding_rate is not None:
assert len(df) == 3
assert df.iloc[0]['open_fund'] == futures_funding_rate
assert df.iloc[1]['open_fund'] == futures_funding_rate
assert df.iloc[2]['open_fund'] == futures_funding_rate
else:
assert len(df) == 0
def test_get_or_calculate_liquidation_price(mocker, default_conf):
api_mock = MagicMock()

View File

@@ -174,6 +174,7 @@ def test_stoploss_adjust_ftx(mocker, default_conf, sl1, sl2, sl3, side):
assert not exchange.stoploss_adjust(sl3, order, side=side)
@pytest.mark.usefixtures("init_persistence")
def test_fetch_stoploss_order_ftx(default_conf, mocker, limit_sell_order, limit_buy_order):
default_conf['dry_run'] = True
order = MagicMock()

View File

@@ -34,6 +34,7 @@ def test_validate_order_types_gateio(default_conf, mocker):
ExchangeResolver.load_exchange('gateio', default_conf, True)
@pytest.mark.usefixtures("init_persistence")
def test_fetch_stoploss_order_gateio(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, id='gateio')

View File

@@ -22,7 +22,7 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
default_conf.update({
"stake_amount": 100.0,
"dry_run_wallet": 1000.0,
"strategy": "StrategyTestV2"
"strategy": "StrategyTestV3"
})
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])

View File

@@ -17,7 +17,7 @@ from freqtrade.optimize.hyperopt_auto import HyperOptAuto
from freqtrade.optimize.hyperopt_tools import HyperoptTools
from freqtrade.optimize.optimize_reports import generate_strategy_stats
from freqtrade.optimize.space import SKDecimal
from freqtrade.strategy.hyper import IntParameter
from freqtrade.strategy import IntParameter
from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, log_has_re, patch_exchange,
patched_configuration_load_config_file)

View File

@@ -972,6 +972,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
'exchange': 'binance',
'leverage': 1.0,
'interest_rate': 0.0,
'liquidation_price': None,
'funding_fees': None,
'trading_mode': ANY,
'orders': [ANY],
@@ -1175,6 +1176,7 @@ def test_api_force_entry(botclient, mocker, fee, endpoint):
'exchange': 'binance',
'leverage': None,
'interest_rate': None,
'liquidation_price': None,
'funding_fees': None,
'trading_mode': 'spot',
'orders': [],

View File

@@ -1,6 +1,7 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
from datetime import datetime
from typing import Optional
import talib.abstract as ta
from pandas import DataFrame
@@ -151,7 +152,8 @@ class StrategyTestV2(IStrategy):
return dataframe
def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, min_stake: float, max_stake: float, **kwargs):
current_profit: float,
min_stake: Optional[float], max_stake: float, **kwargs):
if current_profit < -0.0075:
orders = trade.select_filled_orders('buy')

View File

@@ -1,6 +1,7 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
from datetime import datetime
from typing import Optional
import talib.abstract as ta
from pandas import DataFrame
@@ -185,7 +186,8 @@ class StrategyTestV3(IStrategy):
return 3.0
def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, min_stake: float, max_stake: float, **kwargs):
current_profit: float,
min_stake: Optional[float], max_stake: float, **kwargs):
if current_profit < -0.0075:
orders = trade.select_filled_orders(trade.entry_side)

View File

@@ -16,8 +16,8 @@ from freqtrade.exceptions import OperationalException, StrategyError
from freqtrade.optimize.space import SKDecimal
from freqtrade.persistence import PairLocks, Trade
from freqtrade.resolvers import StrategyResolver
from freqtrade.strategy.hyper import (BaseParameter, BooleanParameter, CategoricalParameter,
DecimalParameter, IntParameter, RealParameter)
from freqtrade.strategy.parameters import (BaseParameter, BooleanParameter, CategoricalParameter,
DecimalParameter, IntParameter, RealParameter)
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from tests.conftest import CURRENT_TEST_STRATEGY, TRADE_SIDES, log_has, log_has_re
@@ -495,37 +495,113 @@ def test_custom_exit(default_conf, fee, caplog) -> None:
enter=False, exit_=False,
low=None, high=None)
assert res.exit_flag is False
assert res.exit_type == ExitType.NONE
assert res == []
strategy.custom_exit = MagicMock(return_value=True)
res = strategy.should_exit(trade, 1, now,
enter=False, exit_=False,
low=None, high=None)
assert res.exit_flag is True
assert res.exit_type == ExitType.CUSTOM_EXIT
assert res.exit_reason == 'custom_exit'
assert res[0].exit_flag is True
assert res[0].exit_type == ExitType.CUSTOM_EXIT
assert res[0].exit_reason == 'custom_exit'
strategy.custom_exit = MagicMock(return_value='hello world')
res = strategy.should_exit(trade, 1, now,
enter=False, exit_=False,
low=None, high=None)
assert res.exit_type == ExitType.CUSTOM_EXIT
assert res.exit_flag is True
assert res.exit_reason == 'hello world'
assert res[0].exit_type == ExitType.CUSTOM_EXIT
assert res[0].exit_flag is True
assert res[0].exit_reason == 'hello world'
caplog.clear()
strategy.custom_exit = MagicMock(return_value='h' * 100)
res = strategy.should_exit(trade, 1, now,
enter=False, exit_=False,
low=None, high=None)
assert res.exit_type == ExitType.CUSTOM_EXIT
assert res.exit_flag is True
assert res.exit_reason == 'h' * 64
assert res[0].exit_type == ExitType.CUSTOM_EXIT
assert res[0].exit_flag is True
assert res[0].exit_reason == 'h' * 64
assert log_has_re('Custom exit reason returned from custom_exit is too long.*', caplog)
def test_should_sell(default_conf, fee) -> None:
strategy = StrategyResolver.load_strategy(default_conf)
trade = Trade(
pair='ETH/BTC',
stake_amount=0.01,
amount=1,
open_date=arrow.utcnow().shift(hours=-1).datetime,
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange='binance',
open_rate=1,
)
now = arrow.utcnow().datetime
res = strategy.should_exit(trade, 1, now,
enter=False, exit_=False,
low=None, high=None)
assert res == []
strategy.min_roi_reached = MagicMock(return_value=True)
res = strategy.should_exit(trade, 1, now,
enter=False, exit_=False,
low=None, high=None)
assert len(res) == 1
assert res == [ExitCheckTuple(exit_type=ExitType.ROI)]
strategy.min_roi_reached = MagicMock(return_value=True)
strategy.stop_loss_reached = MagicMock(
return_value=ExitCheckTuple(exit_type=ExitType.STOP_LOSS))
res = strategy.should_exit(trade, 1, now,
enter=False, exit_=False,
low=None, high=None)
assert len(res) == 2
assert res == [
ExitCheckTuple(exit_type=ExitType.STOP_LOSS),
ExitCheckTuple(exit_type=ExitType.ROI),
]
strategy.custom_exit = MagicMock(return_value='hello world')
# custom-exit and exit-signal is first
res = strategy.should_exit(trade, 1, now,
enter=False, exit_=False,
low=None, high=None)
assert len(res) == 3
assert res == [
ExitCheckTuple(exit_type=ExitType.CUSTOM_EXIT, exit_reason='hello world'),
ExitCheckTuple(exit_type=ExitType.STOP_LOSS),
ExitCheckTuple(exit_type=ExitType.ROI),
]
strategy.stop_loss_reached = MagicMock(
return_value=ExitCheckTuple(exit_type=ExitType.TRAILING_STOP_LOSS))
# Regular exit signal
res = strategy.should_exit(trade, 1, now,
enter=False, exit_=True,
low=None, high=None)
assert len(res) == 3
assert res == [
ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL),
ExitCheckTuple(exit_type=ExitType.ROI),
ExitCheckTuple(exit_type=ExitType.TRAILING_STOP_LOSS),
]
# Regular exit signal, no ROI
strategy.min_roi_reached = MagicMock(return_value=False)
res = strategy.should_exit(trade, 1, now,
enter=False, exit_=True,
low=None, high=None)
assert len(res) == 2
assert res == [
ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL),
ExitCheckTuple(exit_type=ExitType.TRAILING_STOP_LOSS),
]
@pytest.mark.parametrize('side', TRADE_SIDES)
def test_leverage_callback(default_conf, side) -> None:
default_conf['strategy'] = 'StrategyTestV2'

View File

@@ -224,12 +224,12 @@ def test_strategy_override_process_only_new_candles(caplog, default_conf):
default_conf.update({
'strategy': CURRENT_TEST_STRATEGY,
'process_only_new_candles': True
'process_only_new_candles': False
})
strategy = StrategyResolver.load_strategy(default_conf)
assert strategy.process_only_new_candles
assert log_has("Override strategy 'process_only_new_candles' with value in config file: True.",
assert not strategy.process_only_new_candles
assert log_has("Override strategy 'process_only_new_candles' with value in config file: False.",
caplog)

View File

@@ -3050,6 +3050,7 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_order
trade.entry_side = "buy"
trade.open_rate = 200
trade.entry_side = "buy"
trade.open_order_id = "open_order_noop"
l_order['filled'] = 0.0
l_order['status'] = 'open'
reason = CANCEL_REASON['TIMEOUT']
@@ -3596,7 +3597,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(
assert rpc_mock.call_count == 3
assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.ENTRY
assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.ENTRY_FILL
assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.EXIT
assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.EXIT_FILL
@pytest.mark.parametrize(
@@ -4805,9 +4806,6 @@ def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_s
freqtrade.config['dry_run'] = False
freqtrade.startup_update_open_orders()
assert log_has_re(r"Error updating Order .*", caplog)
caplog.clear()
assert len(Order.get_open_orders()) == 3
matching_buy_order = mock_order_4(is_short=is_short)
matching_buy_order.update({
@@ -4818,6 +4816,11 @@ def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_s
# Only stoploss and sell orders are kept open
assert len(Order.get_open_orders()) == 2
caplog.clear()
mocker.patch('freqtrade.exchange.Exchange.fetch_order', side_effect=InvalidOrderException)
freqtrade.startup_update_open_orders()
assert log_has_re(r"Error updating Order .*", caplog)
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short", [False, True])

View File

@@ -52,8 +52,8 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
side_effect=[stoploss_order_closed, stoploss_order_open, stoploss_order_open])
# Sell 3rd trade (not called for the first trade)
should_sell_mock = MagicMock(side_effect=[
ExitCheckTuple(exit_type=ExitType.NONE),
ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)]
[],
[ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)]]
)
cancel_order_mock = MagicMock()
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
@@ -160,11 +160,11 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati
_notify_exit=MagicMock(),
)
should_sell_mock = MagicMock(side_effect=[
ExitCheckTuple(exit_type=ExitType.NONE),
ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL),
ExitCheckTuple(exit_type=ExitType.NONE),
ExitCheckTuple(exit_type=ExitType.NONE),
ExitCheckTuple(exit_type=ExitType.NONE)]
[],
[ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)],
[],
[],
[]]
)
mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock)

View File

@@ -13,7 +13,7 @@ from sqlalchemy import create_engine, text
from freqtrade import constants
from freqtrade.enums import TradingMode
from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.persistence import LocalTrade, Order, Trade, clean_dry_run_db, init_db
from freqtrade.persistence import LocalTrade, Order, Trade, init_db
from freqtrade.persistence.migrations import get_last_sequence_ids, set_sequence_ids
from freqtrade.persistence.models import PairLock
from tests.conftest import create_mock_trades, create_mock_trades_with_leverage, log_has, log_has_re
@@ -24,7 +24,7 @@ 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'], default_conf['dry_run'])
init_db(default_conf['db_url'])
assert hasattr(Trade, '_session')
assert 'scoped_session' in type(Trade._session).__name__
@@ -36,7 +36,7 @@ def test_init_custom_db_url(default_conf, tmpdir):
default_conf.update({'db_url': f'sqlite:///{filename}'})
init_db(default_conf['db_url'], default_conf['dry_run'])
init_db(default_conf['db_url'])
assert Path(filename).is_file()
r = Trade._session.execute(text("PRAGMA journal_mode"))
assert r.first() == ('wal',)
@@ -45,10 +45,10 @@ def test_init_custom_db_url(default_conf, tmpdir):
def test_init_invalid_db_url():
# Update path to a value other than default, but still in-memory
with pytest.raises(OperationalException, match=r'.*no valid database URL*'):
init_db('unknown:///some.url', True)
init_db('unknown:///some.url')
with pytest.raises(OperationalException, match=r'Bad db-url.*For in-memory database, pl.*'):
init_db('sqlite:///', True)
init_db('sqlite:///')
def test_init_prod_db(default_conf, mocker):
@@ -57,7 +57,7 @@ def test_init_prod_db(default_conf, mocker):
create_engine_mock = mocker.patch('freqtrade.persistence.models.create_engine', MagicMock())
init_db(default_conf['db_url'], default_conf['dry_run'])
init_db(default_conf['db_url'])
assert create_engine_mock.call_count == 1
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tradesv3.sqlite'
@@ -70,7 +70,7 @@ def test_init_dryrun_db(default_conf, tmpdir):
'db_url': f'sqlite:///{filename}'
})
init_db(default_conf['db_url'], default_conf['dry_run'])
init_db(default_conf['db_url'])
assert Path(filename).is_file()
@@ -1129,56 +1129,6 @@ def test_calc_profit(
assert pytest.approx(trade.calc_profit_ratio(rate=close_rate)) == round(profit_ratio, 8)
@pytest.mark.usefixtures("init_persistence")
def test_clean_dry_run_db(default_conf, fee):
# Simulate dry_run entries
trade = Trade(
pair='ADA/USDT',
stake_amount=0.001,
amount=123.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
exchange='binance',
open_order_id='dry_run_buy_12345'
)
Trade.query.session.add(trade)
trade = Trade(
pair='ETC/BTC',
stake_amount=0.001,
amount=123.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
exchange='binance',
open_order_id='dry_run_sell_12345'
)
Trade.query.session.add(trade)
# Simulate prod entry
trade = Trade(
pair='ETC/BTC',
stake_amount=0.001,
amount=123.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
exchange='binance',
open_order_id='prod_buy_12345'
)
Trade.query.session.add(trade)
# We have 3 entries: 2 dry_run, 1 prod
assert len(Trade.query.filter(Trade.open_order_id.isnot(None)).all()) == 3
clean_dry_run_db()
# We have now only the prod
assert len(Trade.query.filter(Trade.open_order_id.isnot(None)).all()) == 1
def test_migrate_new(mocker, default_conf, fee, caplog):
"""
Test Database migration (starting with new pairformat)
@@ -1310,7 +1260,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
connection.execute(text("create table trades_bak1 as select * from trades"))
# Run init to test migration
init_db(default_conf['db_url'], default_conf['dry_run'])
init_db(default_conf['db_url'])
assert len(Trade.query.filter(Trade.id == 1).all()) == 1
trade = Trade.query.filter(Trade.id == 1).first()
@@ -1393,7 +1343,7 @@ def test_migrate_too_old(mocker, default_conf, fee, caplog):
# Run init to test migration
with pytest.raises(OperationalException, match=r'Your database seems to be very old'):
init_db(default_conf['db_url'], default_conf['dry_run'])
init_db(default_conf['db_url'])
def test_migrate_get_last_sequence_ids():
@@ -1467,7 +1417,7 @@ def test_migrate_pairlocks(mocker, default_conf, fee, caplog):
connection.execute(text(create_index2))
connection.execute(text(create_index3))
init_db(default_conf['db_url'], default_conf['dry_run'])
init_db(default_conf['db_url'])
assert len(PairLock.query.all()) == 2
assert len(PairLock.query.filter(PairLock.pair == '*').all()) == 1
@@ -2724,3 +2674,21 @@ def test_select_filled_orders(fee):
orders = trades[4].select_filled_orders('sell')
assert orders is not None
assert len(orders) == 0
@pytest.mark.usefixtures("init_persistence")
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_resp = Order.order_by_id(limit_buy_order_open['id'])
assert order_resp
raw_order = order_resp.to_ccxt_object()
del raw_order['fee']
del raw_order['datetime']
del raw_order['info']
del limit_buy_order_open['datetime']
assert raw_order == limit_buy_order_open