mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-10 02:12:01 +00:00
ruff format: update more tests
This commit is contained in:
parent
40e161a5b9
commit
02075b15e3
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue
Block a user