ruff format: update more tests

This commit is contained in:
Matthias 2024-05-12 16:04:01 +02:00
parent 40e161a5b9
commit 02075b15e3
4 changed files with 524 additions and 499 deletions

View File

@ -19,13 +19,10 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
* 2nd trade is kept
* 3rd trade is sold via sell-signal
"""
default_conf['max_open_trades'] = 3
default_conf['exchange']['name'] = 'binance'
default_conf["max_open_trades"] = 3
default_conf["exchange"]["name"] = "binance"
stoploss = {
'id': 123,
'info': {}
}
stoploss = {"id": 123, "info": {}}
stoploss_order_open = {
"id": "123",
"timestamp": 1542707426845,
@ -42,21 +39,17 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
"remaining": 0.0,
"status": "open",
"fee": None,
"trades": None
"trades": None,
}
stoploss_order_closed = stoploss_order_open.copy()
stoploss_order_closed['status'] = 'closed'
stoploss_order_closed['filled'] = stoploss_order_closed['amount']
stoploss_order_closed["status"] = "closed"
stoploss_order_closed["filled"] = stoploss_order_closed["amount"]
# Sell first trade based on stoploss, keep 2nd and 3rd trade open
stop_orders = [stoploss_order_closed, stoploss_order_open.copy(), stoploss_order_open.copy()]
stoploss_order_mock = MagicMock(
side_effect=stop_orders)
stoploss_order_mock = MagicMock(side_effect=stop_orders)
# Sell 3rd trade (not called for the first trade)
should_sell_mock = MagicMock(side_effect=[
[],
[ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)]]
)
should_sell_mock = MagicMock(side_effect=[[], [ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)]])
cancel_order_mock = MagicMock()
mocker.patch.multiple(
EXMS,
@ -70,7 +63,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
)
mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot',
"freqtrade.freqtradebot.FreqtradeBot",
create_stoploss_order=MagicMock(return_value=True),
_notify_exit=MagicMock(),
)
@ -80,9 +73,9 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
mocker.patch("freqtrade.wallets.Wallets.check_exit_amount", return_value=True)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
freqtrade.strategy.order_types["stoploss_on_exchange"] = True
# Switch ordertype to market to close trade immediately
freqtrade.strategy.order_types['exit'] = 'market'
freqtrade.strategy.order_types["exit"] = "market"
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
patch_get_signal(freqtrade)
@ -98,8 +91,8 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
# Make sure stoploss-order is open and trade is bought
for idx, trade in enumerate(trades):
stop_order = stop_orders[idx]
stop_order['id'] = f"stop{idx}"
oobj = Order.parse_from_ccxt_object(stop_order, trade.pair, 'stoploss')
stop_order["id"] = f"stop{idx}"
oobj = Order.parse_from_ccxt_object(stop_order, trade.pair, "stoploss")
oobj.ft_is_open = True
trade.orders.append(oobj)
@ -132,10 +125,13 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
assert not trade.is_open
@pytest.mark.parametrize("balance_ratio,result1", [
(1, 200),
(0.99, 198),
])
@pytest.mark.parametrize(
"balance_ratio,result1",
[
(1, 200),
(0.99, 198),
],
)
def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_ratio, result1) -> None:
"""
Tests workflow unlimited stake-amount
@ -143,14 +139,14 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati
Sell one trade, calculated stake amount should now be lower than before since
one trade was sold at a loss.
"""
default_conf['max_open_trades'] = 5
default_conf['force_entry_enable'] = True
default_conf['stake_amount'] = 'unlimited'
default_conf['tradable_balance_ratio'] = balance_ratio
default_conf['dry_run_wallet'] = 1000
default_conf['exchange']['name'] = 'binance'
default_conf['telegram']['enabled'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
default_conf["max_open_trades"] = 5
default_conf["force_entry_enable"] = True
default_conf["stake_amount"] = "unlimited"
default_conf["tradable_balance_ratio"] = balance_ratio
default_conf["dry_run_wallet"] = 1000
default_conf["exchange"]["name"] = "binance"
default_conf["telegram"]["enabled"] = True
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
mocker.patch.multiple(
EXMS,
fetch_ticker=ticker,
@ -160,24 +156,20 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati
)
mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot',
"freqtrade.freqtradebot.FreqtradeBot",
create_stoploss_order=MagicMock(return_value=True),
_notify_exit=MagicMock(),
)
should_sell_mock = MagicMock(side_effect=[
[],
[ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)],
[],
[],
[]]
should_sell_mock = MagicMock(
side_effect=[[], [ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)], [], [], []]
)
mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
rpc = RPC(freqtrade)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
freqtrade.strategy.order_types["stoploss_on_exchange"] = True
# Switch ordertype to market to close trade immediately
freqtrade.strategy.order_types['exit'] = 'market'
freqtrade.strategy.order_types["exit"] = "market"
patch_get_signal(freqtrade)
# Create 4 trades
@ -186,9 +178,9 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati
trades = Trade.session.scalars(select(Trade)).all()
assert len(trades) == 4
assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC', 5) == result1
assert freqtrade.wallets.get_trade_stake_amount("XRP/BTC", 5) == result1
rpc._rpc_force_entry('TKN/BTC', None)
rpc._rpc_force_entry("TKN/BTC", None)
trades = Trade.session.scalars(select(Trade)).all()
assert len(trades) == 5
@ -206,18 +198,18 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati
# One trade sold
assert len(trades) == 4
# stake-amount should now be reduced, since one trade was sold at a loss.
assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC', 5) < result1
assert freqtrade.wallets.get_trade_stake_amount("XRP/BTC", 5) < result1
# Validate that balance of sold trade is not in dry-run balances anymore.
bals2 = freqtrade.wallets.get_all_balances()
assert bals != bals2
assert len(bals) == 6
assert len(bals2) == 5
assert 'LTC' in bals
assert 'LTC' not in bals2
assert "LTC" in bals
assert "LTC" not in bals2
def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
default_conf_usdt['position_adjustment_enable'] = True
default_conf_usdt["position_adjustment_enable"] = True
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mocker.patch.multiple(
@ -242,8 +234,8 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
# Reduce bid amount
ticker_usdt_modif = ticker_usdt.return_value
ticker_usdt_modif['bid'] = ticker_usdt_modif['bid'] * 0.995
mocker.patch(f'{EXMS}.fetch_ticker', return_value=ticker_usdt_modif)
ticker_usdt_modif["bid"] = ticker_usdt_modif["bid"] * 0.995
mocker.patch(f"{EXMS}.fetch_ticker", return_value=ticker_usdt_modif)
# additional buy order
freqtrade.process()
@ -263,7 +255,7 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
assert len(trade.orders) == 2
assert pytest.approx(trade.stake_amount) == 120
assert trade.orders[0].amount == 30
assert pytest.approx(trade.orders[1].amount) == 60 / ticker_usdt_modif['bid']
assert pytest.approx(trade.orders[1].amount) == 60 / ticker_usdt_modif["bid"]
assert pytest.approx(trade.amount) == trade.orders[0].amount + trade.orders[1].amount
assert trade.nr_of_successful_buys == 2
@ -275,10 +267,10 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
trade = Trade.get_trades().first()
assert trade.is_open is False
assert trade.orders[0].amount == 30
assert trade.orders[0].side == 'buy'
assert pytest.approx(trade.orders[1].amount) == 60 / ticker_usdt_modif['bid']
assert trade.orders[0].side == "buy"
assert pytest.approx(trade.orders[1].amount) == 60 / ticker_usdt_modif["bid"]
# Sold everything
assert trade.orders[-1].side == 'sell'
assert trade.orders[-1].side == "sell"
assert trade.orders[2].amount == trade.amount
assert trade.nr_of_successful_buys == 2
@ -286,7 +278,7 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
def test_dca_short(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
default_conf_usdt['position_adjustment_enable'] = True
default_conf_usdt["position_adjustment_enable"] = True
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mocker.patch.multiple(
@ -314,8 +306,8 @@ def test_dca_short(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
# Reduce bid amount
ticker_usdt_modif = ticker_usdt.return_value
ticker_usdt_modif['ask'] = ticker_usdt_modif['ask'] * 1.004
mocker.patch(f'{EXMS}.fetch_ticker', return_value=ticker_usdt_modif)
ticker_usdt_modif["ask"] = ticker_usdt_modif["ask"] * 1.004
mocker.patch(f"{EXMS}.fetch_ticker", return_value=ticker_usdt_modif)
# additional buy order
freqtrade.process()
@ -334,7 +326,7 @@ def test_dca_short(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
assert pytest.approx(trade.stake_amount) == 120
assert trade.orders[1].amount == round(60 / ticker_usdt_modif['ask'], 4)
assert trade.orders[1].amount == round(60 / ticker_usdt_modif["ask"], 4)
assert trade.amount == trade.orders[0].amount + trade.orders[1].amount
assert trade.nr_of_successful_entries == 2
@ -345,23 +337,21 @@ def test_dca_short(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
trade = Trade.get_trades().first()
assert trade.is_open is False
# assert trade.orders[0].amount == 30
assert trade.orders[0].side == 'sell'
assert trade.orders[1].amount == round(60 / ticker_usdt_modif['ask'], 4)
assert trade.orders[0].side == "sell"
assert trade.orders[1].amount == round(60 / ticker_usdt_modif["ask"], 4)
# Sold everything
assert trade.orders[-1].side == 'buy'
assert trade.orders[-1].side == "buy"
assert trade.orders[2].amount == trade.amount
assert trade.nr_of_successful_entries == 2
assert trade.nr_of_successful_exits == 1
@pytest.mark.parametrize('leverage', [
1, 2
])
@pytest.mark.parametrize("leverage", [1, 2])
def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker) -> None:
default_conf_usdt['position_adjustment_enable'] = True
default_conf_usdt['trading_mode'] = 'futures'
default_conf_usdt['margin_mode'] = 'isolated'
default_conf_usdt["position_adjustment_enable"] = True
default_conf_usdt["trading_mode"] = "futures"
default_conf_usdt["margin_mode"] = "isolated"
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mocker.patch.multiple(
@ -371,13 +361,13 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y,
)
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False)
mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=False)
mocker.patch(f"{EXMS}.get_max_leverage", return_value=10)
mocker.patch(f"{EXMS}.get_funding_fees", return_value=0)
mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0, 0))
patch_get_signal(freqtrade)
freqtrade.strategy.custom_entry_price = lambda **kwargs: ticker_usdt['ask'] * 0.96
freqtrade.strategy.custom_entry_price = lambda **kwargs: ticker_usdt["ask"] * 0.96
freqtrade.strategy.leverage = MagicMock(return_value=leverage)
freqtrade.strategy.minimal_roi = {0: 0.2}
@ -417,7 +407,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
assert trade.initial_stop_loss_pct == -0.1
# Fill order
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True)
mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=True)
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
@ -433,7 +423,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
# 2nd order - not filling
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=120)
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False)
mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=False)
freqtrade.process()
trade = Trade.get_trades().first()
@ -458,7 +448,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
# Fill DCA order
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=None)
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True)
mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=True)
freqtrade.strategy.adjust_entry_price = MagicMock(side_effect=ValueError)
freqtrade.process()
@ -468,7 +458,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
assert pytest.approx(trade.open_rate) == 1.963153456
assert trade.orders[-1].price == 1.95
assert pytest.approx(trade.orders[-1].cost) == 120 * leverage
assert trade.orders[-1].status == 'closed'
assert trade.orders[-1].status == "closed"
assert pytest.approx(trade.amount) == 91.689215 * leverage
# Check the 2 filled orders equal the above amount
@ -476,14 +466,14 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
assert pytest.approx(trade.orders[-1].amount) == 61.538461232 * leverage
# Full exit
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False)
freqtrade.strategy.custom_exit = MagicMock(return_value='Exit now')
mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=False)
freqtrade.strategy.custom_exit = MagicMock(return_value="Exit now")
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=2.02)
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 5
assert trade.orders[-1].side == trade.exit_side
assert trade.orders[-1].status == 'open'
assert trade.orders[-1].status == "open"
assert trade.orders[-1].price == 2.02
assert pytest.approx(trade.amount) == 91.689215 * leverage
assert pytest.approx(trade.orders[-1].amount) == 91.689215 * leverage
@ -492,23 +482,23 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 5
assert trade.orders[-1].status == 'open'
assert trade.orders[-1].status == "open"
assert trade.orders[-1].price == 2.02
# Adjust entry price cannot be called - this is an exit order
assert freqtrade.strategy.adjust_entry_price.call_count == 0
@pytest.mark.parametrize('leverage', [1, 2])
@pytest.mark.parametrize("leverage", [1, 2])
@pytest.mark.parametrize("is_short", [False, True])
def test_dca_order_adjust_entry_replace_fails(
default_conf_usdt, ticker_usdt, fee, mocker, caplog, is_short, leverage
) -> None:
spot = leverage == 1
if not spot:
default_conf_usdt['trading_mode'] = 'futures'
default_conf_usdt['margin_mode'] = 'isolated'
default_conf_usdt['position_adjustment_enable'] = True
default_conf_usdt['max_open_trades'] = 2
default_conf_usdt["trading_mode"] = "futures"
default_conf_usdt["margin_mode"] = "isolated"
default_conf_usdt["position_adjustment_enable"] = True
default_conf_usdt["max_open_trades"] = 2
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mocker.patch.multiple(
EXMS,
@ -518,7 +508,7 @@ def test_dca_order_adjust_entry_replace_fails(
)
# no order fills.
mocker.patch(f'{EXMS}._dry_is_price_crossed', side_effect=[False, True])
mocker.patch(f"{EXMS}._dry_is_price_crossed", side_effect=[False, True])
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
@ -527,10 +517,10 @@ def test_dca_order_adjust_entry_replace_fails(
.where(Order.ft_is_open.is_(True))
.where(Order.ft_order_side != "stoploss")
.where(Order.ft_trade_id == Trade.id)
).all()
).all()
assert len(trades) == 1
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False)
mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=False)
# Timeout to not interfere
freqtrade.strategy.ft_check_timed_out = MagicMock(return_value=False)
@ -538,7 +528,7 @@ def test_dca_order_adjust_entry_replace_fails(
# Create DCA order for 2nd trade (so we have 2 open orders on 2 trades)
# this 2nd order won't fill.
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(20, 'PeNF'))
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(20, "PeNF"))
freqtrade.process()
@ -548,7 +538,7 @@ def test_dca_order_adjust_entry_replace_fails(
.where(Order.ft_is_open.is_(True))
.where(Order.ft_order_side != "stoploss")
.where(Order.ft_trade_id == Trade.id)
).all()
).all()
assert len(trades) == 2
# We now have 2 orders open
@ -559,7 +549,7 @@ def test_dca_order_adjust_entry_replace_fails(
.where(Order.ft_is_open.is_(True))
.where(Order.ft_order_side != "stoploss")
.where(Order.ft_trade_id == Trade.id)
).all()
).all()
assert len(trades) == 2
assert len(Order.get_open_orders()) == 2
# Entry adjustment is called
@ -568,8 +558,9 @@ def test_dca_order_adjust_entry_replace_fails(
# Attempt order replacement - fails.
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=1234)
entry_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_entry',
return_value=False)
entry_mock = mocker.patch(
"freqtrade.freqtradebot.FreqtradeBot.execute_entry", return_value=False
)
msg = r"Could not replace order for.*"
assert not log_has_re(msg, caplog)
freqtrade.manage_open_orders()
@ -580,13 +571,13 @@ def test_dca_order_adjust_entry_replace_fails(
assert len(Order.get_open_orders()) == 0
@pytest.mark.parametrize('leverage', [1, 2])
@pytest.mark.parametrize("leverage", [1, 2])
def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, leverage) -> None:
default_conf_usdt['position_adjustment_enable'] = True
default_conf_usdt["position_adjustment_enable"] = True
spot = leverage == 1
if not spot:
default_conf_usdt['trading_mode'] = 'futures'
default_conf_usdt['margin_mode'] = 'isolated'
default_conf_usdt["trading_mode"] = "futures"
default_conf_usdt["margin_mode"] = "isolated"
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
assert freqtrade.trading_mode == TradingMode.FUTURES if not spot else TradingMode.SPOT
mocker.patch.multiple(
@ -599,7 +590,7 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
get_funding_fees=MagicMock(return_value=0),
)
mocker.patch(f"{EXMS}.get_max_leverage", return_value=10)
starting_amount = freqtrade.wallets.get_total('USDT')
starting_amount = freqtrade.wallets.get_total("USDT")
assert starting_amount == 1000
patch_get_signal(freqtrade)
@ -613,11 +604,11 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
assert trade.leverage == leverage
assert pytest.approx(trade.amount) == 30.0 * leverage
assert trade.open_rate == 2.0
assert pytest.approx(freqtrade.wallets.get_free('USDT')) == starting_amount - 60
assert pytest.approx(freqtrade.wallets.get_free("USDT")) == starting_amount - 60
if spot:
assert pytest.approx(freqtrade.wallets.get_total('USDT')) == starting_amount - 60
assert pytest.approx(freqtrade.wallets.get_total("USDT")) == starting_amount - 60
else:
assert freqtrade.wallets.get_total('USDT') == starting_amount
assert freqtrade.wallets.get_total("USDT") == starting_amount
# Too small size
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-59)
@ -627,28 +618,29 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
assert pytest.approx(trade.stake_amount) == 60
assert pytest.approx(trade.amount) == 30.0 * leverage
assert log_has_re(
r"Remaining amount of \d\.\d+.* would be smaller than the minimum of 10.", caplog)
r"Remaining amount of \d\.\d+.* would be smaller than the minimum of 10.", caplog
)
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(-20, 'PES'))
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(-20, "PES"))
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
assert trade.orders[-1].ft_order_side == 'sell'
assert trade.orders[-1].ft_order_tag == 'PES'
assert trade.orders[-1].ft_order_side == "sell"
assert trade.orders[-1].ft_order_tag == "PES"
assert pytest.approx(trade.stake_amount) == 40
assert pytest.approx(trade.amount) == 20 * leverage
assert trade.open_rate == 2.0
assert trade.is_open
assert trade.realized_profit > 0.098 * leverage
expected_profit = starting_amount - 40 + trade.realized_profit
assert pytest.approx(freqtrade.wallets.get_free('USDT')) == expected_profit
assert pytest.approx(freqtrade.wallets.get_free("USDT")) == expected_profit
if spot:
assert pytest.approx(freqtrade.wallets.get_total('USDT')) == expected_profit
assert pytest.approx(freqtrade.wallets.get_total("USDT")) == expected_profit
else:
# total won't change in futures mode, only free / used will.
assert freqtrade.wallets.get_total('USDT') == starting_amount + trade.realized_profit
assert freqtrade.wallets.get_total("USDT") == starting_amount + trade.realized_profit
caplog.clear()
# Sell more than what we got (we got ~20 coins left)
@ -666,14 +658,13 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
assert len(trade.orders) == 2
# Amount exactly comes out as exactly 0
freqtrade.strategy.adjust_trade_position = MagicMock(
return_value=-trade.stake_amount)
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-trade.stake_amount)
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 3
assert trade.orders[-1].ft_order_side == 'sell'
assert trade.orders[-1].ft_order_side == "sell"
assert pytest.approx(trade.stake_amount) == 40
assert trade.is_open is False
@ -683,14 +674,14 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 3
assert trade.orders[-1].ft_order_side == 'sell'
assert trade.orders[-1].ft_order_side == "sell"
assert pytest.approx(trade.stake_amount) == 40
assert trade.is_open is False
assert log_has_re('Amount to exit is 0.0 due to exchange limits - not exiting.', caplog)
assert log_has_re("Amount to exit is 0.0 due to exchange limits - not exiting.", caplog)
expected_profit = starting_amount - 60 + trade.realized_profit
assert pytest.approx(freqtrade.wallets.get_free('USDT')) == expected_profit
assert pytest.approx(freqtrade.wallets.get_free("USDT")) == expected_profit
if spot:
assert pytest.approx(freqtrade.wallets.get_total('USDT')) == expected_profit
assert pytest.approx(freqtrade.wallets.get_total("USDT")) == expected_profit
else:
# total won't change in futures mode, only free / used will.
assert freqtrade.wallets.get_total('USDT') == starting_amount + trade.realized_profit
assert freqtrade.wallets.get_total("USDT") == starting_amount + trade.realized_profit

View File

@ -9,96 +9,103 @@ from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver
def test_hyperoptlossresolver_noname(default_conf):
with pytest.raises(OperationalException,
match="No Hyperopt loss set. Please use `--hyperopt-loss` to specify "
"the Hyperopt-Loss class to use."):
with pytest.raises(
OperationalException,
match="No Hyperopt loss set. Please use `--hyperopt-loss` to specify "
"the Hyperopt-Loss class to use.",
):
HyperOptLossResolver.load_hyperoptloss(default_conf)
def test_hyperoptlossresolver(mocker, default_conf) -> None:
hl = ShortTradeDurHyperOptLoss
mocker.patch(
'freqtrade.resolvers.hyperopt_resolver.HyperOptLossResolver.load_object',
MagicMock(return_value=hl())
"freqtrade.resolvers.hyperopt_resolver.HyperOptLossResolver.load_object",
MagicMock(return_value=hl()),
)
default_conf.update({'hyperopt_loss': 'SharpeHyperOptLossDaily'})
default_conf.update({"hyperopt_loss": "SharpeHyperOptLossDaily"})
x = HyperOptLossResolver.load_hyperoptloss(default_conf)
assert hasattr(x, "hyperopt_loss_function")
def test_hyperoptlossresolver_wrongname(default_conf) -> None:
default_conf.update({'hyperopt_loss': "NonExistingLossClass"})
default_conf.update({"hyperopt_loss": "NonExistingLossClass"})
with pytest.raises(OperationalException, match=r'Impossible to load HyperoptLoss.*'):
with pytest.raises(OperationalException, match=r"Impossible to load HyperoptLoss.*"):
HyperOptLossResolver.load_hyperoptloss(default_conf)
def test_loss_calculation_prefer_correct_trade_count(hyperopt_conf, hyperopt_results) -> None:
hyperopt_conf.update({'hyperopt_loss': "ShortTradeDurHyperOptLoss"})
hyperopt_conf.update({"hyperopt_loss": "ShortTradeDurHyperOptLoss"})
hl = HyperOptLossResolver.load_hyperoptloss(hyperopt_conf)
correct = hl.hyperopt_loss_function(hyperopt_results, 600,
datetime(2019, 1, 1), datetime(2019, 5, 1))
over = hl.hyperopt_loss_function(hyperopt_results, 600 + 100,
datetime(2019, 1, 1), datetime(2019, 5, 1))
under = hl.hyperopt_loss_function(hyperopt_results, 600 - 100,
datetime(2019, 1, 1), datetime(2019, 5, 1))
correct = hl.hyperopt_loss_function(
hyperopt_results, 600, datetime(2019, 1, 1), datetime(2019, 5, 1)
)
over = hl.hyperopt_loss_function(
hyperopt_results, 600 + 100, datetime(2019, 1, 1), datetime(2019, 5, 1)
)
under = hl.hyperopt_loss_function(
hyperopt_results, 600 - 100, datetime(2019, 1, 1), datetime(2019, 5, 1)
)
assert over > correct
assert under > correct
def test_loss_calculation_prefer_shorter_trades(hyperopt_conf, hyperopt_results) -> None:
resultsb = hyperopt_results.copy()
resultsb.loc[1, 'trade_duration'] = 20
resultsb.loc[1, "trade_duration"] = 20
hyperopt_conf.update({'hyperopt_loss': "ShortTradeDurHyperOptLoss"})
hyperopt_conf.update({"hyperopt_loss": "ShortTradeDurHyperOptLoss"})
hl = HyperOptLossResolver.load_hyperoptloss(hyperopt_conf)
longer = hl.hyperopt_loss_function(hyperopt_results, 100,
datetime(2019, 1, 1), datetime(2019, 5, 1))
shorter = hl.hyperopt_loss_function(resultsb, 100,
datetime(2019, 1, 1), datetime(2019, 5, 1))
longer = hl.hyperopt_loss_function(
hyperopt_results, 100, datetime(2019, 1, 1), datetime(2019, 5, 1)
)
shorter = hl.hyperopt_loss_function(resultsb, 100, datetime(2019, 1, 1), datetime(2019, 5, 1))
assert shorter < longer
def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) -> None:
results_over = hyperopt_results.copy()
results_over['profit_ratio'] = hyperopt_results['profit_ratio'] * 2
results_over["profit_ratio"] = hyperopt_results["profit_ratio"] * 2
results_under = hyperopt_results.copy()
results_under['profit_ratio'] = hyperopt_results['profit_ratio'] / 2
results_under["profit_ratio"] = hyperopt_results["profit_ratio"] / 2
hyperopt_conf.update({'hyperopt_loss': "ShortTradeDurHyperOptLoss"})
hyperopt_conf.update({"hyperopt_loss": "ShortTradeDurHyperOptLoss"})
hl = HyperOptLossResolver.load_hyperoptloss(hyperopt_conf)
correct = hl.hyperopt_loss_function(hyperopt_results, 600,
datetime(2019, 1, 1), datetime(2019, 5, 1))
over = hl.hyperopt_loss_function(results_over, 600,
datetime(2019, 1, 1), datetime(2019, 5, 1))
under = hl.hyperopt_loss_function(results_under, 600,
datetime(2019, 1, 1), datetime(2019, 5, 1))
correct = hl.hyperopt_loss_function(
hyperopt_results, 600, datetime(2019, 1, 1), datetime(2019, 5, 1)
)
over = hl.hyperopt_loss_function(results_over, 600, datetime(2019, 1, 1), datetime(2019, 5, 1))
under = hl.hyperopt_loss_function(
results_under, 600, datetime(2019, 1, 1), datetime(2019, 5, 1)
)
assert over < correct
assert under > correct
@pytest.mark.parametrize('lossfunction', [
"OnlyProfitHyperOptLoss",
"SortinoHyperOptLoss",
"SortinoHyperOptLossDaily",
"SharpeHyperOptLoss",
"SharpeHyperOptLossDaily",
"MaxDrawDownHyperOptLoss",
"MaxDrawDownRelativeHyperOptLoss",
"CalmarHyperOptLoss",
"ProfitDrawDownHyperOptLoss",
])
@pytest.mark.parametrize(
"lossfunction",
[
"OnlyProfitHyperOptLoss",
"SortinoHyperOptLoss",
"SortinoHyperOptLossDaily",
"SharpeHyperOptLoss",
"SharpeHyperOptLossDaily",
"MaxDrawDownHyperOptLoss",
"MaxDrawDownRelativeHyperOptLoss",
"CalmarHyperOptLoss",
"ProfitDrawDownHyperOptLoss",
],
)
def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunction) -> None:
results_over = hyperopt_results.copy()
results_over['profit_abs'] = hyperopt_results['profit_abs'] * 2 + 0.2
results_over['profit_ratio'] = hyperopt_results['profit_ratio'] * 2
results_over["profit_abs"] = hyperopt_results["profit_abs"] * 2 + 0.2
results_over["profit_ratio"] = hyperopt_results["profit_ratio"] * 2
results_under = hyperopt_results.copy()
results_under['profit_abs'] = hyperopt_results['profit_abs'] / 2 - 0.2
results_under['profit_ratio'] = hyperopt_results['profit_ratio'] / 2
results_under["profit_abs"] = hyperopt_results["profit_abs"] / 2 - 0.2
results_under["profit_ratio"] = hyperopt_results["profit_ratio"] / 2
default_conf.update({'hyperopt_loss': lossfunction})
default_conf.update({"hyperopt_loss": lossfunction})
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
correct = hl.hyperopt_loss_function(
hyperopt_results,
@ -107,7 +114,7 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct
max_date=datetime(2019, 5, 1),
config=default_conf,
processed=None,
backtest_stats={'profit_total': hyperopt_results['profit_abs'].sum()}
backtest_stats={"profit_total": hyperopt_results["profit_abs"].sum()},
)
over = hl.hyperopt_loss_function(
results_over,
@ -116,7 +123,7 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct
max_date=datetime(2019, 5, 1),
config=default_conf,
processed=None,
backtest_stats={'profit_total': results_over['profit_abs'].sum()}
backtest_stats={"profit_total": results_over["profit_abs"].sum()},
)
under = hl.hyperopt_loss_function(
results_under,
@ -125,7 +132,7 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct
max_date=datetime(2019, 5, 1),
config=default_conf,
processed=None,
backtest_stats={'profit_total': results_under['profit_abs'].sum()}
backtest_stats={"profit_total": results_under["profit_abs"].sum()},
)
assert over < correct
assert under > correct

View File

@ -51,7 +51,7 @@ def _backup_file(file: Path, copy_file: bool = False) -> None:
:param copy_file: keep file in place too.
:return: None
"""
file_swp = str(file) + '.swp'
file_swp = str(file) + ".swp"
if file.is_file():
file.rename(file_swp)
@ -60,149 +60,172 @@ def _backup_file(file: Path, copy_file: bool = False) -> None:
def test_text_table_bt_results():
results = pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
'profit_ratio': [0.1, 0.2, -0.05],
'profit_abs': [0.2, 0.4, -0.1],
'trade_duration': [10, 30, 20],
"pair": ["ETH/BTC", "ETH/BTC", "ETH/BTC"],
"profit_ratio": [0.1, 0.2, -0.05],
"profit_abs": [0.2, 0.4, -0.1],
"trade_duration": [10, 30, 20],
}
)
result_str = (
'| Pair | Entries | Avg Profit % | Tot Profit BTC | '
'Tot Profit % | Avg Duration | Win Draw Loss Win% |\n'
'|---------+-----------+----------------+------------------+'
'----------------+----------------+-------------------------|\n'
'| ETH/BTC | 3 | 8.33 | 0.50000000 | '
'12.50 | 0:20:00 | 2 0 1 66.7 |\n'
'| TOTAL | 3 | 8.33 | 0.50000000 | '
'12.50 | 0:20:00 | 2 0 1 66.7 |'
"| Pair | Entries | Avg Profit % | Tot Profit BTC | "
"Tot Profit % | Avg Duration | Win Draw Loss Win% |\n"
"|---------+-----------+----------------+------------------+"
"----------------+----------------+-------------------------|\n"
"| ETH/BTC | 3 | 8.33 | 0.50000000 | "
"12.50 | 0:20:00 | 2 0 1 66.7 |\n"
"| TOTAL | 3 | 8.33 | 0.50000000 | "
"12.50 | 0:20:00 | 2 0 1 66.7 |"
)
pair_results = generate_pair_metrics(['ETH/BTC'], stake_currency='BTC',
starting_balance=4, results=results)
assert text_table_bt_results(pair_results, stake_currency='BTC') == result_str
pair_results = generate_pair_metrics(
["ETH/BTC"], stake_currency="BTC", starting_balance=4, results=results
)
assert text_table_bt_results(pair_results, stake_currency="BTC") == result_str
def test_generate_backtest_stats(default_conf, testdatadir, tmp_path):
default_conf.update({'strategy': CURRENT_TEST_STRATEGY})
default_conf.update({"strategy": CURRENT_TEST_STRATEGY})
StrategyResolver.load_strategy(default_conf)
results = {'DefStrat': {
'results': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC",
"UNITTEST/BTC", "UNITTEST/BTC"],
"profit_ratio": [0.003312, 0.010801, 0.013803, 0.002780],
"profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
"open_date": [dt_utc(2017, 11, 14, 19, 32, 00),
dt_utc(2017, 11, 14, 21, 36, 00),
dt_utc(2017, 11, 14, 22, 12, 00),
dt_utc(2017, 11, 14, 22, 44, 00)],
"close_date": [dt_utc(2017, 11, 14, 21, 35, 00),
dt_utc(2017, 11, 14, 22, 10, 00),
dt_utc(2017, 11, 14, 22, 43, 00),
dt_utc(2017, 11, 14, 22, 58, 00)],
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"trade_duration": [123, 34, 31, 14],
"is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01],
"exit_reason": [ExitType.ROI, ExitType.STOP_LOSS,
ExitType.ROI, ExitType.FORCE_EXIT]
}),
'config': default_conf,
'locks': [],
'final_balance': 1000.02,
'rejected_signals': 20,
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
'canceled_trade_entries': 0,
'canceled_entry_orders': 0,
'replaced_entry_orders': 0,
'backtest_start_time': dt_ts() // 1000,
'backtest_end_time': dt_ts() // 1000,
'run_id': '123',
results = {
"DefStrat": {
"results": pd.DataFrame(
{
"pair": ["UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC"],
"profit_ratio": [0.003312, 0.010801, 0.013803, 0.002780],
"profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
"open_date": [
dt_utc(2017, 11, 14, 19, 32, 00),
dt_utc(2017, 11, 14, 21, 36, 00),
dt_utc(2017, 11, 14, 22, 12, 00),
dt_utc(2017, 11, 14, 22, 44, 00),
],
"close_date": [
dt_utc(2017, 11, 14, 21, 35, 00),
dt_utc(2017, 11, 14, 22, 10, 00),
dt_utc(2017, 11, 14, 22, 43, 00),
dt_utc(2017, 11, 14, 22, 58, 00),
],
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"trade_duration": [123, 34, 31, 14],
"is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01],
"exit_reason": [
ExitType.ROI,
ExitType.STOP_LOSS,
ExitType.ROI,
ExitType.FORCE_EXIT,
],
}
),
"config": default_conf,
"locks": [],
"final_balance": 1000.02,
"rejected_signals": 20,
"timedout_entry_orders": 0,
"timedout_exit_orders": 0,
"canceled_trade_entries": 0,
"canceled_entry_orders": 0,
"replaced_entry_orders": 0,
"backtest_start_time": dt_ts() // 1000,
"backtest_end_time": dt_ts() // 1000,
"run_id": "123",
}
}
timerange = TimeRange.parse_timerange('1510688220-1510700340')
}
timerange = TimeRange.parse_timerange("1510688220-1510700340")
min_date = dt_from_ts(1510688220)
max_date = dt_from_ts(1510700340)
btdata = history.load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange,
fill_up_missing=True)
btdata = history.load_data(
testdatadir, "1m", ["UNITTEST/BTC"], timerange=timerange, fill_up_missing=True
)
stats = generate_backtest_stats(btdata, results, min_date, max_date)
assert isinstance(stats, dict)
assert 'strategy' in stats
assert 'DefStrat' in stats['strategy']
assert 'strategy_comparison' in stats
strat_stats = stats['strategy']['DefStrat']
assert strat_stats['backtest_start'] == min_date.strftime(DATETIME_PRINT_FORMAT)
assert strat_stats['backtest_end'] == max_date.strftime(DATETIME_PRINT_FORMAT)
assert strat_stats['total_trades'] == len(results['DefStrat']['results'])
assert "strategy" in stats
assert "DefStrat" in stats["strategy"]
assert "strategy_comparison" in stats
strat_stats = stats["strategy"]["DefStrat"]
assert strat_stats["backtest_start"] == min_date.strftime(DATETIME_PRINT_FORMAT)
assert strat_stats["backtest_end"] == max_date.strftime(DATETIME_PRINT_FORMAT)
assert strat_stats["total_trades"] == len(results["DefStrat"]["results"])
# Above sample had no losing trade
assert strat_stats['max_drawdown_account'] == 0.0
assert strat_stats["max_drawdown_account"] == 0.0
# Retry with losing trade
results = {'DefStrat': {
'results': pd.DataFrame(
{"pair": ["UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC"],
"profit_ratio": [0.003312, 0.010801, -0.013803, 0.002780],
"profit_abs": [0.000003, 0.000011, -0.000014, 0.000003],
"open_date": [dt_utc(2017, 11, 14, 19, 32, 00),
dt_utc(2017, 11, 14, 21, 36, 00),
dt_utc(2017, 11, 14, 22, 12, 00),
dt_utc(2017, 11, 14, 22, 44, 00)],
"close_date": [dt_utc(2017, 11, 14, 21, 35, 00),
dt_utc(2017, 11, 14, 22, 10, 00),
dt_utc(2017, 11, 14, 22, 43, 00),
dt_utc(2017, 11, 14, 22, 58, 00)],
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
"close_rate": [0.002546, 0.003014, 0.0032903, 0.003217],
"trade_duration": [123, 34, 31, 14],
"is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01],
"exit_reason": [ExitType.ROI, ExitType.ROI,
ExitType.STOP_LOSS, ExitType.FORCE_EXIT]
}),
'config': default_conf,
'locks': [],
'final_balance': 1000.02,
'rejected_signals': 20,
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
'canceled_trade_entries': 0,
'canceled_entry_orders': 0,
'replaced_entry_orders': 0,
'backtest_start_time': dt_ts() // 1000,
'backtest_end_time': dt_ts() // 1000,
'run_id': '124',
results = {
"DefStrat": {
"results": pd.DataFrame(
{
"pair": ["UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC"],
"profit_ratio": [0.003312, 0.010801, -0.013803, 0.002780],
"profit_abs": [0.000003, 0.000011, -0.000014, 0.000003],
"open_date": [
dt_utc(2017, 11, 14, 19, 32, 00),
dt_utc(2017, 11, 14, 21, 36, 00),
dt_utc(2017, 11, 14, 22, 12, 00),
dt_utc(2017, 11, 14, 22, 44, 00),
],
"close_date": [
dt_utc(2017, 11, 14, 21, 35, 00),
dt_utc(2017, 11, 14, 22, 10, 00),
dt_utc(2017, 11, 14, 22, 43, 00),
dt_utc(2017, 11, 14, 22, 58, 00),
],
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
"close_rate": [0.002546, 0.003014, 0.0032903, 0.003217],
"trade_duration": [123, 34, 31, 14],
"is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01],
"exit_reason": [
ExitType.ROI,
ExitType.ROI,
ExitType.STOP_LOSS,
ExitType.FORCE_EXIT,
],
}
),
"config": default_conf,
"locks": [],
"final_balance": 1000.02,
"rejected_signals": 20,
"timedout_entry_orders": 0,
"timedout_exit_orders": 0,
"canceled_trade_entries": 0,
"canceled_entry_orders": 0,
"replaced_entry_orders": 0,
"backtest_start_time": dt_ts() // 1000,
"backtest_end_time": dt_ts() // 1000,
"run_id": "124",
}
}
stats = generate_backtest_stats(btdata, results, min_date, max_date)
assert isinstance(stats, dict)
assert 'strategy' in stats
assert 'DefStrat' in stats['strategy']
assert 'strategy_comparison' in stats
strat_stats = stats['strategy']['DefStrat']
assert "strategy" in stats
assert "DefStrat" in stats["strategy"]
assert "strategy_comparison" in stats
strat_stats = stats["strategy"]["DefStrat"]
assert pytest.approx(strat_stats['max_drawdown_account']) == 1.399999e-08
assert strat_stats['drawdown_start'] == '2017-11-14 22:10:00'
assert strat_stats['drawdown_end'] == '2017-11-14 22:43:00'
assert strat_stats['drawdown_end_ts'] == 1510699380000
assert strat_stats['drawdown_start_ts'] == 1510697400000
assert strat_stats['pairlist'] == ['UNITTEST/BTC']
assert pytest.approx(strat_stats["max_drawdown_account"]) == 1.399999e-08
assert strat_stats["drawdown_start"] == "2017-11-14 22:10:00"
assert strat_stats["drawdown_end"] == "2017-11-14 22:43:00"
assert strat_stats["drawdown_end_ts"] == 1510699380000
assert strat_stats["drawdown_start_ts"] == 1510697400000
assert strat_stats["pairlist"] == ["UNITTEST/BTC"]
# Test storing stats
filename = tmp_path / 'btresult.json'
filename = tmp_path / "btresult.json"
filename_last = tmp_path / LAST_BT_RESULT_FN
_backup_file(filename_last, copy_file=True)
assert not filename.is_file()
store_backtest_stats(filename, stats, '2022_01_01_15_05_13')
store_backtest_stats(filename, stats, "2022_01_01_15_05_13")
# get real Filename (it's btresult-<date>.json)
last_fn = get_latest_backtest_filename(filename_last.parent)
@ -211,9 +234,9 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmp_path):
filename1 = tmp_path / last_fn
assert filename1.is_file()
content = filename1.read_text()
assert 'max_drawdown_account' in content
assert 'strategy' in content
assert 'pairlist' in content
assert "max_drawdown_account" in content
assert "strategy" in content
assert "pairlist" in content
assert filename_last.is_file()
@ -222,149 +245,146 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmp_path):
def test_store_backtest_stats(testdatadir, mocker):
dump_mock = mocker.patch("freqtrade.optimize.optimize_reports.bt_storage.file_dump_json")
dump_mock = mocker.patch('freqtrade.optimize.optimize_reports.bt_storage.file_dump_json')
data = {'metadata': {}, 'strategy': {}, 'strategy_comparison': []}
store_backtest_stats(testdatadir, data, '2022_01_01_15_05_13')
data = {"metadata": {}, "strategy": {}, "strategy_comparison": []}
store_backtest_stats(testdatadir, data, "2022_01_01_15_05_13")
assert dump_mock.call_count == 3
assert isinstance(dump_mock.call_args_list[0][0][0], Path)
assert str(dump_mock.call_args_list[0][0][0]).startswith(str(testdatadir / 'backtest-result'))
assert str(dump_mock.call_args_list[0][0][0]).startswith(str(testdatadir / "backtest-result"))
dump_mock.reset_mock()
filename = testdatadir / 'testresult.json'
store_backtest_stats(filename, data, '2022_01_01_15_05_13')
filename = testdatadir / "testresult.json"
store_backtest_stats(filename, data, "2022_01_01_15_05_13")
assert dump_mock.call_count == 3
assert isinstance(dump_mock.call_args_list[0][0][0], Path)
# result will be testdatadir / testresult-<timestamp>.json
assert str(dump_mock.call_args_list[0][0][0]).startswith(str(testdatadir / 'testresult'))
assert str(dump_mock.call_args_list[0][0][0]).startswith(str(testdatadir / "testresult"))
def test_store_backtest_stats_real(tmp_path):
data = {'metadata': {}, 'strategy': {}, 'strategy_comparison': []}
store_backtest_stats(tmp_path, data, '2022_01_01_15_05_13')
data = {"metadata": {}, "strategy": {}, "strategy_comparison": []}
store_backtest_stats(tmp_path, data, "2022_01_01_15_05_13")
assert (tmp_path / 'backtest-result-2022_01_01_15_05_13.json').is_file()
assert (tmp_path / 'backtest-result-2022_01_01_15_05_13.meta.json').is_file()
assert not (tmp_path / 'backtest-result-2022_01_01_15_05_13_market_change.feather').is_file()
assert (tmp_path / "backtest-result-2022_01_01_15_05_13.json").is_file()
assert (tmp_path / "backtest-result-2022_01_01_15_05_13.meta.json").is_file()
assert not (tmp_path / "backtest-result-2022_01_01_15_05_13_market_change.feather").is_file()
assert (tmp_path / LAST_BT_RESULT_FN).is_file()
fn = get_latest_backtest_filename(tmp_path)
assert fn == 'backtest-result-2022_01_01_15_05_13.json'
assert fn == "backtest-result-2022_01_01_15_05_13.json"
store_backtest_stats(tmp_path, data, '2024_01_01_15_05_25', market_change_data=pd.DataFrame())
assert (tmp_path / 'backtest-result-2024_01_01_15_05_25.json').is_file()
assert (tmp_path / 'backtest-result-2024_01_01_15_05_25.meta.json').is_file()
assert (tmp_path / 'backtest-result-2024_01_01_15_05_25_market_change.feather').is_file()
store_backtest_stats(tmp_path, data, "2024_01_01_15_05_25", market_change_data=pd.DataFrame())
assert (tmp_path / "backtest-result-2024_01_01_15_05_25.json").is_file()
assert (tmp_path / "backtest-result-2024_01_01_15_05_25.meta.json").is_file()
assert (tmp_path / "backtest-result-2024_01_01_15_05_25_market_change.feather").is_file()
assert (tmp_path / LAST_BT_RESULT_FN).is_file()
# Last file reference should be updated
fn = get_latest_backtest_filename(tmp_path)
assert fn == 'backtest-result-2024_01_01_15_05_25.json'
assert fn == "backtest-result-2024_01_01_15_05_25.json"
def test_store_backtest_candles(testdatadir, mocker):
dump_mock = mocker.patch("freqtrade.optimize.optimize_reports.bt_storage.file_dump_joblib")
dump_mock = mocker.patch(
'freqtrade.optimize.optimize_reports.bt_storage.file_dump_joblib')
candle_dict = {'DefStrat': {'UNITTEST/BTC': pd.DataFrame()}}
candle_dict = {"DefStrat": {"UNITTEST/BTC": pd.DataFrame()}}
# mock directory exporting
store_backtest_analysis_results(testdatadir, candle_dict, {}, '2022_01_01_15_05_13')
store_backtest_analysis_results(testdatadir, candle_dict, {}, "2022_01_01_15_05_13")
assert dump_mock.call_count == 2
assert isinstance(dump_mock.call_args_list[0][0][0], Path)
assert str(dump_mock.call_args_list[0][0][0]).endswith('_signals.pkl')
assert str(dump_mock.call_args_list[0][0][0]).endswith("_signals.pkl")
dump_mock.reset_mock()
# mock file exporting
filename = Path(testdatadir / 'testresult')
store_backtest_analysis_results(filename, candle_dict, {}, '2022_01_01_15_05_13')
filename = Path(testdatadir / "testresult")
store_backtest_analysis_results(filename, candle_dict, {}, "2022_01_01_15_05_13")
assert dump_mock.call_count == 2
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('_signals.pkl')
assert str(dump_mock.call_args_list[0][0][0]).endswith("_signals.pkl")
dump_mock.reset_mock()
def test_write_read_backtest_candles(tmp_path):
candle_dict = {'DefStrat': {'UNITTEST/BTC': pd.DataFrame()}}
candle_dict = {"DefStrat": {"UNITTEST/BTC": pd.DataFrame()}}
# test directory exporting
sample_date = '2022_01_01_15_05_13'
sample_date = "2022_01_01_15_05_13"
store_backtest_analysis_results(tmp_path, candle_dict, {}, sample_date)
stored_file = tmp_path / f'backtest-result-{sample_date}_signals.pkl'
stored_file = tmp_path / f"backtest-result-{sample_date}_signals.pkl"
with stored_file.open("rb") as scp:
pickled_signal_candles = joblib.load(scp)
assert pickled_signal_candles.keys() == candle_dict.keys()
assert pickled_signal_candles['DefStrat'].keys() == pickled_signal_candles['DefStrat'].keys()
assert pickled_signal_candles['DefStrat']['UNITTEST/BTC'] \
.equals(pickled_signal_candles['DefStrat']['UNITTEST/BTC'])
assert pickled_signal_candles["DefStrat"].keys() == pickled_signal_candles["DefStrat"].keys()
assert pickled_signal_candles["DefStrat"]["UNITTEST/BTC"].equals(
pickled_signal_candles["DefStrat"]["UNITTEST/BTC"]
)
_clean_test_file(stored_file)
# test file exporting
filename = tmp_path / 'testresult'
filename = tmp_path / "testresult"
store_backtest_analysis_results(filename, candle_dict, {}, sample_date)
stored_file = tmp_path / f'testresult-{sample_date}_signals.pkl'
stored_file = tmp_path / f"testresult-{sample_date}_signals.pkl"
with stored_file.open("rb") as scp:
pickled_signal_candles = joblib.load(scp)
assert pickled_signal_candles.keys() == candle_dict.keys()
assert pickled_signal_candles['DefStrat'].keys() == pickled_signal_candles['DefStrat'].keys()
assert pickled_signal_candles['DefStrat']['UNITTEST/BTC'] \
.equals(pickled_signal_candles['DefStrat']['UNITTEST/BTC'])
assert pickled_signal_candles["DefStrat"].keys() == pickled_signal_candles["DefStrat"].keys()
assert pickled_signal_candles["DefStrat"]["UNITTEST/BTC"].equals(
pickled_signal_candles["DefStrat"]["UNITTEST/BTC"]
)
_clean_test_file(stored_file)
def test_generate_pair_metrics():
results = pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC'],
'profit_ratio': [0.1, 0.2],
'profit_abs': [0.2, 0.4],
'trade_duration': [10, 30],
'wins': [2, 0],
'draws': [0, 0],
'losses': [0, 0]
"pair": ["ETH/BTC", "ETH/BTC"],
"profit_ratio": [0.1, 0.2],
"profit_abs": [0.2, 0.4],
"trade_duration": [10, 30],
"wins": [2, 0],
"draws": [0, 0],
"losses": [0, 0],
}
)
pair_results = generate_pair_metrics(['ETH/BTC'], stake_currency='BTC',
starting_balance=2, results=results)
pair_results = generate_pair_metrics(
["ETH/BTC"], stake_currency="BTC", starting_balance=2, results=results
)
assert isinstance(pair_results, list)
assert len(pair_results) == 2
assert pair_results[-1]['key'] == 'TOTAL'
assert pair_results[-1]["key"] == "TOTAL"
assert (
pytest.approx(pair_results[-1]['profit_mean_pct']) == pair_results[-1]['profit_mean'] * 100)
assert (
pytest.approx(pair_results[-1]['profit_sum_pct']) == pair_results[-1]['profit_sum'] * 100)
pytest.approx(pair_results[-1]["profit_mean_pct"]) == pair_results[-1]["profit_mean"] * 100
)
assert pytest.approx(pair_results[-1]["profit_sum_pct"]) == pair_results[-1]["profit_sum"] * 100
def test_generate_daily_stats(testdatadir):
filename = testdatadir / "backtest_results/backtest-result.json"
bt_data = load_backtest_data(filename)
res = generate_daily_stats(bt_data)
assert isinstance(res, dict)
assert round(res['backtest_best_day'], 4) == 0.1796
assert round(res['backtest_worst_day'], 4) == -0.1468
assert res['winning_days'] == 19
assert res['draw_days'] == 0
assert res['losing_days'] == 2
assert round(res["backtest_best_day"], 4) == 0.1796
assert round(res["backtest_worst_day"], 4) == -0.1468
assert res["winning_days"] == 19
assert res["draw_days"] == 0
assert res["losing_days"] == 2
# Select empty dataframe!
res = generate_daily_stats(bt_data.loc[bt_data['open_date'] == '2000-01-01', :])
res = generate_daily_stats(bt_data.loc[bt_data["open_date"] == "2000-01-01", :])
assert isinstance(res, dict)
assert round(res['backtest_best_day'], 4) == 0.0
assert res['winning_days'] == 0
assert res['draw_days'] == 0
assert res['losing_days'] == 0
assert round(res["backtest_best_day"], 4) == 0.0
assert res["winning_days"] == 0
assert res["draw_days"] == 0
assert res["losing_days"] == 0
def test_generate_trading_stats(testdatadir):
@ -372,22 +392,24 @@ def test_generate_trading_stats(testdatadir):
bt_data = load_backtest_data(filename)
res = generate_trading_stats(bt_data)
assert isinstance(res, dict)
assert res['winner_holding_avg'] == timedelta(seconds=1440)
assert res['loser_holding_avg'] == timedelta(days=1, seconds=21420)
assert 'wins' in res
assert 'losses' in res
assert 'draws' in res
assert res["winner_holding_avg"] == timedelta(seconds=1440)
assert res["loser_holding_avg"] == timedelta(days=1, seconds=21420)
assert "wins" in res
assert "losses" in res
assert "draws" in res
# Select empty dataframe!
res = generate_trading_stats(bt_data.loc[bt_data['open_date'] == '2000-01-01', :])
assert res['wins'] == 0
assert res['losses'] == 0
res = generate_trading_stats(bt_data.loc[bt_data["open_date"] == "2000-01-01", :])
assert res["wins"] == 0
assert res["losses"] == 0
def test_calc_streak(testdatadir):
df = pd.DataFrame({
'profit_ratio': [0.05, -0.02, -0.03, -0.05, 0.01, 0.02, 0.03, 0.04, -0.02, -0.03],
})
df = pd.DataFrame(
{
"profit_ratio": [0.05, -0.02, -0.03, -0.05, 0.01, 0.02, 0.03, 0.04, -0.02, -0.03],
}
)
# 4 consecutive wins, 3 consecutive losses
res = calc_streak(df)
assert res == (4, 3)
@ -396,12 +418,14 @@ def test_calc_streak(testdatadir):
# invert situation
df1 = df.copy()
df1['profit_ratio'] = df1['profit_ratio'] * -1
df1["profit_ratio"] = df1["profit_ratio"] * -1
assert calc_streak(df1) == (3, 4)
df_empty = pd.DataFrame({
'profit_ratio': [],
})
df_empty = pd.DataFrame(
{
"profit_ratio": [],
}
)
assert df_empty.empty
assert calc_streak(df_empty) == (0, 0)
@ -411,131 +435,133 @@ def test_calc_streak(testdatadir):
def test_text_table_exit_reason():
results = pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
'profit_ratio': [0.1, 0.2, -0.1],
'profit_abs': [0.2, 0.4, -0.2],
'trade_duration': [10, 30, 10],
'wins': [2, 0, 0],
'draws': [0, 0, 0],
'losses': [0, 0, 1],
'exit_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS]
"pair": ["ETH/BTC", "ETH/BTC", "ETH/BTC"],
"profit_ratio": [0.1, 0.2, -0.1],
"profit_abs": [0.2, 0.4, -0.2],
"trade_duration": [10, 30, 10],
"wins": [2, 0, 0],
"draws": [0, 0, 0],
"losses": [0, 0, 1],
"exit_reason": [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS],
}
)
result_str = (
'| Exit Reason | Exits | Avg Profit % | Tot Profit BTC | Tot Profit % |'
' Avg Duration | Win Draw Loss Win% |\n'
'|---------------+---------+----------------+------------------+----------------+'
'----------------+-------------------------|\n'
'| roi | 2 | 15.00 | 0.60000000 | 2.73 |'
' 0:20:00 | 2 0 0 100 |\n'
'| stop_loss | 1 | -10.00 | -0.20000000 | -0.91 |'
' 0:10:00 | 0 0 1 0 |\n'
'| TOTAL | 3 | 6.67 | 0.40000000 | 1.82 |'
' 0:17:00 | 2 0 1 66.7 |'
"| Exit Reason | Exits | Avg Profit % | Tot Profit BTC | Tot Profit % |"
" Avg Duration | Win Draw Loss Win% |\n"
"|---------------+---------+----------------+------------------+----------------+"
"----------------+-------------------------|\n"
"| roi | 2 | 15.00 | 0.60000000 | 2.73 |"
" 0:20:00 | 2 0 0 100 |\n"
"| stop_loss | 1 | -10.00 | -0.20000000 | -0.91 |"
" 0:10:00 | 0 0 1 0 |\n"
"| TOTAL | 3 | 6.67 | 0.40000000 | 1.82 |"
" 0:17:00 | 2 0 1 66.7 |"
)
exit_reason_stats = generate_tag_metrics('exit_reason', starting_balance=22,
results=results, skip_nan=False)
assert text_table_tags('exit_tag', exit_reason_stats, 'BTC') == result_str
exit_reason_stats = generate_tag_metrics(
"exit_reason", starting_balance=22, results=results, skip_nan=False
)
assert text_table_tags("exit_tag", exit_reason_stats, "BTC") == result_str
def test_generate_sell_reason_stats():
results = pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
'profit_ratio': [0.1, 0.2, -0.1],
'profit_abs': [0.2, 0.4, -0.2],
'trade_duration': [10, 30, 10],
'wins': [2, 0, 0],
'draws': [0, 0, 0],
'losses': [0, 0, 1],
'exit_reason': [ExitType.ROI.value, ExitType.ROI.value, ExitType.STOP_LOSS.value]
"pair": ["ETH/BTC", "ETH/BTC", "ETH/BTC"],
"profit_ratio": [0.1, 0.2, -0.1],
"profit_abs": [0.2, 0.4, -0.2],
"trade_duration": [10, 30, 10],
"wins": [2, 0, 0],
"draws": [0, 0, 0],
"losses": [0, 0, 1],
"exit_reason": [ExitType.ROI.value, ExitType.ROI.value, ExitType.STOP_LOSS.value],
}
)
exit_reason_stats = generate_tag_metrics('exit_reason', starting_balance=22,
results=results, skip_nan=False)
exit_reason_stats = generate_tag_metrics(
"exit_reason", starting_balance=22, results=results, skip_nan=False
)
roi_result = exit_reason_stats[0]
assert roi_result['key'] == 'roi'
assert roi_result['trades'] == 2
assert pytest.approx(roi_result['profit_mean']) == 0.15
assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2)
assert pytest.approx(roi_result['profit_mean']) == 0.15
assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2)
assert roi_result["key"] == "roi"
assert roi_result["trades"] == 2
assert pytest.approx(roi_result["profit_mean"]) == 0.15
assert roi_result["profit_mean_pct"] == round(roi_result["profit_mean"] * 100, 2)
assert pytest.approx(roi_result["profit_mean"]) == 0.15
assert roi_result["profit_mean_pct"] == round(roi_result["profit_mean"] * 100, 2)
stop_result = exit_reason_stats[1]
assert stop_result['key'] == 'stop_loss'
assert stop_result['trades'] == 1
assert pytest.approx(stop_result['profit_mean']) == -0.1
assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2)
assert pytest.approx(stop_result['profit_mean']) == -0.1
assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2)
assert stop_result["key"] == "stop_loss"
assert stop_result["trades"] == 1
assert pytest.approx(stop_result["profit_mean"]) == -0.1
assert stop_result["profit_mean_pct"] == round(stop_result["profit_mean"] * 100, 2)
assert pytest.approx(stop_result["profit_mean"]) == -0.1
assert stop_result["profit_mean_pct"] == round(stop_result["profit_mean"] * 100, 2)
def test_text_table_strategy(testdatadir):
filename = testdatadir / "backtest_results/backtest-result_multistrat.json"
bt_res_data = load_backtest_stats(filename)
bt_res_data_comparison = bt_res_data.pop('strategy_comparison')
bt_res_data_comparison = bt_res_data.pop("strategy_comparison")
result_str = (
'| Strategy | Entries | Avg Profit % | Tot Profit BTC |'
' Tot Profit % | Avg Duration | Win Draw Loss Win% | Drawdown |\n'
'|----------------+-----------+----------------+------------------+'
'----------------+----------------+-------------------------+-----------------------|\n'
'| StrategyTestV2 | 179 | 0.08 | 0.02608550 |'
' 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |\n'
'| TestStrategy | 179 | 0.08 | 0.02608550 |'
' 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |'
"| Strategy | Entries | Avg Profit % | Tot Profit BTC |"
" Tot Profit % | Avg Duration | Win Draw Loss Win% | Drawdown |\n"
"|----------------+-----------+----------------+------------------+"
"----------------+----------------+-------------------------+-----------------------|\n"
"| StrategyTestV2 | 179 | 0.08 | 0.02608550 |"
" 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |\n"
"| TestStrategy | 179 | 0.08 | 0.02608550 |"
" 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |"
)
strategy_results = generate_strategy_comparison(bt_stats=bt_res_data['strategy'])
strategy_results = generate_strategy_comparison(bt_stats=bt_res_data["strategy"])
assert strategy_results == bt_res_data_comparison
assert text_table_strategy(strategy_results, 'BTC') == result_str
assert text_table_strategy(strategy_results, "BTC") == result_str
def test_generate_edge_table():
results = {}
results['ETH/BTC'] = PairInfo(-0.01, 0.60, 2, 1, 3, 10, 60)
assert generate_edge_table(results).count('+') == 7
assert generate_edge_table(results).count('| ETH/BTC |') == 1
assert generate_edge_table(results).count(
'| Risk Reward Ratio | Required Risk Reward | Expectancy |') == 1
results["ETH/BTC"] = PairInfo(-0.01, 0.60, 2, 1, 3, 10, 60)
assert generate_edge_table(results).count("+") == 7
assert generate_edge_table(results).count("| ETH/BTC |") == 1
assert (
generate_edge_table(results).count(
"| Risk Reward Ratio | Required Risk Reward | Expectancy |"
)
== 1
)
def test_generate_periodic_breakdown_stats(testdatadir):
filename = testdatadir / "backtest_results/backtest-result.json"
bt_data = load_backtest_data(filename).to_dict(orient='records')
bt_data = load_backtest_data(filename).to_dict(orient="records")
res = generate_periodic_breakdown_stats(bt_data, 'day')
res = generate_periodic_breakdown_stats(bt_data, "day")
assert isinstance(res, list)
assert len(res) == 21
day = res[0]
assert 'date' in day
assert 'draws' in day
assert 'loses' in day
assert 'wins' in day
assert 'profit_abs' in day
assert "date" in day
assert "draws" in day
assert "loses" in day
assert "wins" in day
assert "profit_abs" in day
# Select empty dataframe!
res = generate_periodic_breakdown_stats([], 'day')
res = generate_periodic_breakdown_stats([], "day")
assert res == []
def test__get_resample_from_period():
assert _get_resample_from_period('day') == '1d'
assert _get_resample_from_period('week') == '1W-MON'
assert _get_resample_from_period('month') == '1ME'
assert _get_resample_from_period("day") == "1d"
assert _get_resample_from_period("week") == "1W-MON"
assert _get_resample_from_period("month") == "1ME"
with pytest.raises(ValueError, match=r"Period noooo is not supported."):
_get_resample_from_period('noooo')
_get_resample_from_period("noooo")
for period in BACKTEST_BREAKDOWNS:
assert isinstance(_get_resample_from_period(period), str)
@ -544,11 +570,11 @@ def test__get_resample_from_period():
def test_show_sorted_pairlist(testdatadir, default_conf, capsys):
filename = testdatadir / "backtest_results/backtest-result.json"
bt_data = load_backtest_stats(filename)
default_conf['backtest_show_pair_list'] = True
default_conf["backtest_show_pair_list"] = True
show_sorted_pairlist(default_conf, bt_data)
out, _err = capsys.readouterr()
assert 'Pairs for Strategy StrategyTestV3: \n[' in out
assert 'TOTAL' not in out
assert "Pairs for Strategy StrategyTestV3: \n[" in out
assert "TOTAL" not in out
assert '"ETH/BTC", // ' in out

View File

@ -20,7 +20,7 @@ from tests.conftest import (
@pytest.mark.parametrize("use_db", [True, False])
def test_trade_custom_data(fee, use_db):
if not use_db:
disable_database_use('5m')
disable_database_use("5m")
Trade.reset_trades()
CustomDataWrapper.reset_custom_data()
@ -31,120 +31,121 @@ def test_trade_custom_data(fee, use_db):
trade1.id = 1
assert trade1.get_all_custom_data() == []
trade1.set_custom_data('test_str', 'test_value')
trade1.set_custom_data('test_int', 1)
trade1.set_custom_data('test_float', 1.55)
trade1.set_custom_data('test_bool', True)
trade1.set_custom_data('test_dict', {'test': 'dict'})
trade1.set_custom_data("test_str", "test_value")
trade1.set_custom_data("test_int", 1)
trade1.set_custom_data("test_float", 1.55)
trade1.set_custom_data("test_bool", True)
trade1.set_custom_data("test_dict", {"test": "dict"})
assert len(trade1.get_all_custom_data()) == 5
assert trade1.get_custom_data('test_str') == 'test_value'
trade1.set_custom_data('test_str', 'test_value_updated')
assert trade1.get_custom_data('test_str') == 'test_value_updated'
assert trade1.get_custom_data("test_str") == "test_value"
trade1.set_custom_data("test_str", "test_value_updated")
assert trade1.get_custom_data("test_str") == "test_value_updated"
assert trade1.get_custom_data('test_int') == 1
assert isinstance(trade1.get_custom_data('test_int'), int)
assert trade1.get_custom_data("test_int") == 1
assert isinstance(trade1.get_custom_data("test_int"), int)
assert trade1.get_custom_data('test_float') == 1.55
assert isinstance(trade1.get_custom_data('test_float'), float)
assert trade1.get_custom_data("test_float") == 1.55
assert isinstance(trade1.get_custom_data("test_float"), float)
assert trade1.get_custom_data('test_bool') is True
assert isinstance(trade1.get_custom_data('test_bool'), bool)
assert trade1.get_custom_data("test_bool") is True
assert isinstance(trade1.get_custom_data("test_bool"), bool)
assert trade1.get_custom_data('test_dict') == {'test': 'dict'}
assert isinstance(trade1.get_custom_data('test_dict'), dict)
assert trade1.get_custom_data("test_dict") == {"test": "dict"}
assert isinstance(trade1.get_custom_data("test_dict"), dict)
if not use_db:
enable_database_use()
def test_trade_custom_data_strategy_compat(mocker, default_conf_usdt, fee):
mocker.patch(f'{EXMS}.get_rate', return_value=0.50)
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=None)
default_conf_usdt["minimal_roi"] = {"0": 100}
mocker.patch(f"{EXMS}.get_rate", return_value=0.50)
mocker.patch("freqtrade.freqtradebot.FreqtradeBot.get_real_amount", return_value=None)
default_conf_usdt["minimal_roi"] = {"0": 100}
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
create_mock_trades_usdt(fee)
trade1 = Trade.get_trades_proxy(pair='ADA/USDT')[0]
trade1.set_custom_data('test_str', 'test_value')
trade1.set_custom_data('test_int', 1)
trade1 = Trade.get_trades_proxy(pair="ADA/USDT")[0]
trade1.set_custom_data("test_str", "test_value")
trade1.set_custom_data("test_int", 1)
def custom_exit(pair, trade, **kwargs):
if pair == 'ADA/USDT':
custom_val = trade.get_custom_data('test_str')
custom_val_i = trade.get_custom_data('test_int')
if pair == "ADA/USDT":
custom_val = trade.get_custom_data("test_str")
custom_val_i = trade.get_custom_data("test_int")
return f"{custom_val}_{custom_val_i}"
freqtrade.strategy.custom_exit = custom_exit
ff_spy = mocker.spy(freqtrade.strategy, 'custom_exit')
ff_spy = mocker.spy(freqtrade.strategy, "custom_exit")
trades = Trade.get_open_trades()
freqtrade.exit_positions(trades)
Trade.commit()
trade_after = Trade.get_trades_proxy(pair='ADA/USDT')[0]
assert trade_after.get_custom_data('test_str') == 'test_value'
assert trade_after.get_custom_data('test_int') == 1
trade_after = Trade.get_trades_proxy(pair="ADA/USDT")[0]
assert trade_after.get_custom_data("test_str") == "test_value"
assert trade_after.get_custom_data("test_int") == 1
# 2 open pairs eligible for exit
assert ff_spy.call_count == 2
assert trade_after.exit_reason == 'test_value_1'
assert trade_after.exit_reason == "test_value_1"
def test_trade_custom_data_strategy_backtest_compat(mocker, default_conf_usdt, fee):
mocker.patch(f'{EXMS}.get_fee', fee)
mocker.patch(f"{EXMS}.get_fee", fee)
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=10)
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf"))
mocker.patch(f"{EXMS}.get_max_leverage", return_value=10)
mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0.1, 0.1))
mocker.patch('freqtrade.optimize.backtesting.Backtesting._run_funding_fees')
mocker.patch("freqtrade.optimize.backtesting.Backtesting._run_funding_fees")
patch_exchange(mocker)
default_conf_usdt.update({
"stake_amount": 100.0,
"max_open_trades": 2,
"dry_run_wallet": 1000.0,
"strategy": "StrategyTestV3",
"trading_mode": "futures",
"margin_mode": "isolated",
"stoploss": -2,
"minimal_roi": {"0": 100},
})
default_conf_usdt['pairlists'] = [{'method': 'StaticPairList', 'allow_inactive': True}]
default_conf_usdt.update(
{
"stake_amount": 100.0,
"max_open_trades": 2,
"dry_run_wallet": 1000.0,
"strategy": "StrategyTestV3",
"trading_mode": "futures",
"margin_mode": "isolated",
"stoploss": -2,
"minimal_roi": {"0": 100},
}
)
default_conf_usdt["pairlists"] = [{"method": "StaticPairList", "allow_inactive": True}]
backtesting = Backtesting(default_conf_usdt)
df = generate_test_data(default_conf_usdt['timeframe'], 100, '2022-01-01 00:00:00+00:00')
df = generate_test_data(default_conf_usdt["timeframe"], 100, "2022-01-01 00:00:00+00:00")
pair_exp = 'XRP/USDT:USDT'
pair_exp = "XRP/USDT:USDT"
def custom_exit(pair, trade, **kwargs):
custom_val = trade.get_custom_data('test_str')
custom_val_i = trade.get_custom_data('test_int', 0)
custom_val = trade.get_custom_data("test_str")
custom_val_i = trade.get_custom_data("test_int", 0)
if pair == pair_exp:
trade.set_custom_data('test_str', 'test_value')
trade.set_custom_data('test_int', custom_val_i + 1)
trade.set_custom_data("test_str", "test_value")
trade.set_custom_data("test_int", custom_val_i + 1)
if custom_val_i >= 2:
return f"{custom_val}_{custom_val_i}"
backtesting._set_strategy(backtesting.strategylist[0])
processed = backtesting.strategy.advise_all_indicators({
pair_exp: df,
'BTC/USDT:USDT': df,
})
processed = backtesting.strategy.advise_all_indicators(
{
pair_exp: df,
"BTC/USDT:USDT": df,
}
)
def fun(dataframe, *args, **kwargs):
dataframe.loc[dataframe.index == 50, 'enter_long'] = 1
dataframe.loc[dataframe.index == 50, "enter_long"] = 1
return dataframe
backtesting.strategy.advise_entry = fun
backtesting.strategy.leverage = MagicMock(return_value=1)
backtesting.strategy.custom_exit = custom_exit
ff_spy = mocker.spy(backtesting.strategy, 'custom_exit')
ff_spy = mocker.spy(backtesting.strategy, "custom_exit")
min_date, max_date = get_timerange(processed)
@ -153,13 +154,13 @@ def test_trade_custom_data_strategy_backtest_compat(mocker, default_conf_usdt, f
start_date=min_date,
end_date=max_date,
)
results = result['results']
results = result["results"]
assert not results.empty
assert len(results) == 2
assert results['pair'][0] == pair_exp
assert results['pair'][1] == 'BTC/USDT:USDT'
assert results['exit_reason'][0] == 'test_value_2'
assert results['exit_reason'][1] == 'exit_signal'
assert results["pair"][0] == pair_exp
assert results["pair"][1] == "BTC/USDT:USDT"
assert results["exit_reason"][0] == "test_value_2"
assert results["exit_reason"][1] == "exit_signal"
assert ff_spy.call_count == 7
Backtesting.cleanup()