freqtrade_origin/tests/test_wallets.py

454 lines
16 KiB
Python
Raw Normal View History

2018-11-17 21:58:27 +00:00
# pragma pylint: disable=missing-docstring
2021-04-20 09:31:37 +00:00
from copy import deepcopy
2018-11-17 21:58:27 +00:00
from unittest.mock import MagicMock
2021-04-20 09:31:37 +00:00
import pytest
2023-04-26 05:09:26 +00:00
from sqlalchemy import select
2021-04-20 09:31:37 +00:00
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
from freqtrade.exceptions import DependencyException
2023-04-26 05:09:26 +00:00
from freqtrade.persistence import Trade
2024-05-12 13:08:40 +00:00
from tests.conftest import (
EXMS,
create_mock_trades,
create_mock_trades_usdt,
get_patched_freqtradebot,
patch_wallet,
)
2018-11-17 21:58:27 +00:00
def test_sync_wallet_at_boot(mocker, default_conf):
2024-05-12 13:29:14 +00:00
default_conf["dry_run"] = False
2018-11-17 21:58:27 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2024-05-12 13:29:14 +00:00
get_balances=MagicMock(
return_value={
"BNT": {"free": 1.0, "used": 2.0, "total": 3.0},
"GAS": {"free": 0.260739, "used": 0.0, "total": 0.260739},
"USDT": {"free": 20, "used": 20, "total": 40},
}
),
2018-11-17 21:58:27 +00:00
)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
2020-09-22 17:37:18 +00:00
assert len(freqtrade.wallets._wallets) == 3
2024-05-12 13:29:14 +00:00
assert freqtrade.wallets._wallets["BNT"].free == 1.0
assert freqtrade.wallets._wallets["BNT"].used == 2.0
assert freqtrade.wallets._wallets["BNT"].total == 3.0
assert freqtrade.wallets._wallets["GAS"].free == 0.260739
assert freqtrade.wallets._wallets["GAS"].used == 0.0
assert freqtrade.wallets._wallets["GAS"].total == 0.260739
assert freqtrade.wallets.get_free("BNT") == 1.0
assert "USDT" in freqtrade.wallets._wallets
2023-05-14 15:49:43 +00:00
assert freqtrade.wallets._last_wallet_refresh is not None
2018-11-21 16:54:14 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2024-05-12 13:29:14 +00:00
get_balances=MagicMock(
return_value={
"BNT": {"free": 1.2, "used": 1.9, "total": 3.5},
"GAS": {"free": 0.270739, "used": 0.1, "total": 0.260439},
}
),
2018-11-21 16:54:14 +00:00
)
freqtrade.wallets.update()
2020-09-22 17:37:18 +00:00
# USDT is missing from the 2nd result - so should not be in this either.
2019-02-28 22:26:29 +00:00
assert len(freqtrade.wallets._wallets) == 2
2024-05-12 13:29:14 +00:00
assert freqtrade.wallets._wallets["BNT"].free == 1.2
assert freqtrade.wallets._wallets["BNT"].used == 1.9
assert freqtrade.wallets._wallets["BNT"].total == 3.5
assert freqtrade.wallets._wallets["GAS"].free == 0.270739
assert freqtrade.wallets._wallets["GAS"].used == 0.1
assert freqtrade.wallets._wallets["GAS"].total == 0.260439
assert freqtrade.wallets.get_free("GAS") == 0.270739
assert freqtrade.wallets.get_used("GAS") == 0.1
assert freqtrade.wallets.get_total("GAS") == 0.260439
assert freqtrade.wallets.get_owned("GAS/USDT", "GAS") == 0.260439
2024-05-12 13:29:14 +00:00
update_mock = mocker.patch("freqtrade.wallets.Wallets._update_live")
2020-01-15 05:43:41 +00:00
freqtrade.wallets.update(False)
assert update_mock.call_count == 0
freqtrade.wallets.update()
assert update_mock.call_count == 1
2018-11-21 18:47:28 +00:00
2024-05-12 13:29:14 +00:00
assert freqtrade.wallets.get_free("NOCURRENCY") == 0
assert freqtrade.wallets.get_used("NOCURRENCY") == 0
assert freqtrade.wallets.get_total("NOCURRENCY") == 0
assert freqtrade.wallets.get_owned("NOCURRENCY/USDT", "NOCURRENCY") == 0
2020-10-28 15:29:08 +00:00
2018-11-21 18:47:28 +00:00
def test_sync_wallet_missing_data(mocker, default_conf):
2024-05-12 13:29:14 +00:00
default_conf["dry_run"] = False
2018-11-21 18:47:28 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2024-05-12 13:29:14 +00:00
get_balances=MagicMock(
return_value={
"BNT": {"free": 1.0, "used": 2.0, "total": 3.0},
"GAS": {"free": 0.260739, "total": 0.260739},
}
),
2018-11-21 18:47:28 +00:00
)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
2019-02-28 22:26:29 +00:00
assert len(freqtrade.wallets._wallets) == 2
2024-05-12 13:29:14 +00:00
assert freqtrade.wallets._wallets["BNT"].free == 1.0
assert freqtrade.wallets._wallets["BNT"].used == 2.0
assert freqtrade.wallets._wallets["BNT"].total == 3.0
assert freqtrade.wallets._wallets["GAS"].free == 0.260739
assert freqtrade.wallets._wallets["GAS"].used == 0.0
2024-05-12 13:29:14 +00:00
assert freqtrade.wallets._wallets["GAS"].total == 0.260739
assert freqtrade.wallets.get_free("GAS") == 0.260739
2021-04-20 09:31:37 +00:00
def test_get_trade_stake_amount_no_stake_amount(default_conf, mocker) -> None:
2024-05-12 13:29:14 +00:00
patch_wallet(mocker, free=default_conf["stake_amount"] * 0.5)
2021-04-20 09:31:37 +00:00
freqtrade = get_patched_freqtradebot(mocker, default_conf)
2024-05-12 13:29:14 +00:00
with pytest.raises(DependencyException, match=r".*stake amount.*"):
freqtrade.wallets.get_trade_stake_amount("ETH/BTC", 1)
@pytest.mark.parametrize(
"balance_ratio,capital,result1,result2",
[
(1, None, 50, 66.66666),
(0.99, None, 49.5, 66.0),
(0.50, None, 25, 33.3333),
# Tests with capital ignore balance_ratio
(1, 100, 50, 0.0),
(0.99, 200, 50, 66.66666),
(0.99, 150, 50, 50),
(0.50, 50, 25, 0.0),
(0.50, 10, 5, 0.0),
],
)
def test_get_trade_stake_amount_unlimited_amount(
default_conf,
ticker,
balance_ratio,
capital,
result1,
result2,
limit_buy_order_open,
fee,
mocker,
) -> None:
2021-04-20 09:31:37 +00:00
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2021-04-20 09:31:37 +00:00
fetch_ticker=ticker,
create_order=MagicMock(return_value=limit_buy_order_open),
2024-05-12 13:29:14 +00:00
get_fee=fee,
2021-04-20 09:31:37 +00:00
)
conf = deepcopy(default_conf)
2024-05-12 13:29:14 +00:00
conf["stake_amount"] = UNLIMITED_STAKE_AMOUNT
conf["dry_run_wallet"] = 100
conf["tradable_balance_ratio"] = balance_ratio
2021-07-10 10:39:02 +00:00
if capital is not None:
2024-05-12 13:29:14 +00:00
conf["available_capital"] = capital
2021-04-20 09:31:37 +00:00
freqtrade = get_patched_freqtradebot(mocker, conf)
# no open trades, order amount should be 'balance / max_open_trades'
2024-05-12 13:29:14 +00:00
result = freqtrade.wallets.get_trade_stake_amount("ETH/USDT", 2)
2021-04-20 09:31:37 +00:00
assert result == result1
# create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)'
2024-05-12 13:29:14 +00:00
freqtrade.execute_entry("ETH/USDT", result)
2021-04-20 09:31:37 +00:00
2024-05-12 13:29:14 +00:00
result = freqtrade.wallets.get_trade_stake_amount("LTC/USDT", 2)
2021-04-20 09:31:37 +00:00
assert result == result1
# create 2 trades, order amount should be None
2024-05-12 13:29:14 +00:00
freqtrade.execute_entry("LTC/BTC", result)
2021-04-20 09:31:37 +00:00
2024-05-12 13:29:14 +00:00
result = freqtrade.wallets.get_trade_stake_amount("XRP/USDT", 2)
2021-04-20 09:31:37 +00:00
assert result == 0
2024-05-12 13:29:14 +00:00
freqtrade.config["dry_run_wallet"] = 200
freqtrade.wallets.start_cap = 200
2024-05-12 13:29:14 +00:00
result = freqtrade.wallets.get_trade_stake_amount("XRP/USDT", 3)
assert round(result, 4) == round(result2, 4)
2021-04-20 09:31:37 +00:00
# set max_open_trades = None, so do not trade
2024-05-12 13:29:14 +00:00
result = freqtrade.wallets.get_trade_stake_amount("NEO/USDT", 0)
2021-04-20 09:31:37 +00:00
assert result == 0
2024-05-12 13:29:14 +00:00
@pytest.mark.parametrize(
"stake_amount,min_stake,stake_available,max_stake,trade_amount,expected",
[
(22, 11, 50, 10000, None, 22),
(100, 11, 500, 10000, None, 100),
(1000, 11, 500, 10000, None, 500), # Above stake_available
(700, 11, 1000, 400, None, 400), # Above max_stake, below stake available
(20, 15, 10, 10000, None, 0), # Minimum stake > stake_available
(9, 11, 100, 10000, None, 11), # Below min stake
(1, 15, 10, 10000, None, 0), # Below min stake and min_stake > stake_available
(20, 50, 100, 10000, None, 0), # Below min stake and stake * 1.3 > min_stake
(1000, None, 1000, 10000, None, 1000), # No min-stake-amount could be determined
# Rebuy - resulting in too high stake amount. Adjusting.
(2000, 15, 2000, 3000, 1500, 1500),
],
)
def test_validate_stake_amount(
mocker,
default_conf,
stake_amount,
min_stake,
stake_available,
max_stake,
trade_amount,
expected,
):
freqtrade = get_patched_freqtradebot(mocker, default_conf)
2024-05-12 13:29:14 +00:00
mocker.patch(
"freqtrade.wallets.Wallets.get_available_stake_amount", return_value=stake_available
)
res = freqtrade.wallets.validate_stake_amount(
2024-05-12 13:29:14 +00:00
"XRP/USDT", stake_amount, min_stake, max_stake, trade_amount
)
assert res == expected
2024-05-12 13:29:14 +00:00
@pytest.mark.parametrize(
"available_capital,closed_profit,open_stakes,free,expected",
[
(None, 10, 100, 910, 1000),
(None, 0, 0, 2500, 2500),
(None, 500, 0, 2500, 2000),
(None, 500, 0, 2500, 2000),
(None, -70, 0, 1930, 2000),
# Only available balance matters when it's set.
(100, 0, 0, 0, 100),
(1000, 0, 2, 5, 1000),
(1235, 2250, 2, 5, 1235),
(1235, -2250, 2, 5, 1235),
],
)
def test_get_starting_balance(
mocker, default_conf, available_capital, closed_profit, open_stakes, free, expected
):
if available_capital:
2024-05-12 13:29:14 +00:00
default_conf["available_capital"] = available_capital
mocker.patch(
"freqtrade.persistence.models.Trade.get_total_closed_profit", return_value=closed_profit
)
mocker.patch(
"freqtrade.persistence.models.Trade.total_open_trades_stakes", return_value=open_stakes
)
mocker.patch("freqtrade.wallets.Wallets.get_free", return_value=free)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert freqtrade.wallets.get_starting_balance() == expected
2022-02-23 06:40:15 +00:00
def test_sync_wallet_futures_live(mocker, default_conf):
2024-05-12 13:29:14 +00:00
default_conf["dry_run"] = False
default_conf["trading_mode"] = "futures"
default_conf["margin_mode"] = "isolated"
2022-02-23 06:40:15 +00:00
mock_result = [
{
"symbol": "ETH/USDT:USDT",
"timestamp": None,
"datetime": None,
"initialMargin": 0.0,
"initialMarginPercentage": None,
"maintenanceMargin": 0.0,
"maintenanceMarginPercentage": 0.005,
"entryPrice": 0.0,
"notional": 100.0,
"leverage": 5.0,
"unrealizedPnl": 0.0,
"contracts": 100.0,
"contractSize": 1,
"marginRatio": None,
"liquidationPrice": 0.0,
"markPrice": 2896.41,
"collateral": 20,
"marginType": "isolated",
2024-05-12 13:29:14 +00:00
"side": "short",
"percentage": None,
2022-02-23 06:40:15 +00:00
},
{
"symbol": "ADA/USDT:USDT",
"timestamp": None,
"datetime": None,
"initialMargin": 0.0,
"initialMarginPercentage": None,
"maintenanceMargin": 0.0,
"maintenanceMarginPercentage": 0.005,
"entryPrice": 0.0,
"notional": 100.0,
"leverage": 5.0,
"unrealizedPnl": 0.0,
"contracts": 100.0,
"contractSize": 1,
"marginRatio": None,
"liquidationPrice": 0.0,
"markPrice": 0.91,
"collateral": 20,
"marginType": "isolated",
2024-05-12 13:29:14 +00:00
"side": "short",
"percentage": None,
2022-02-23 06:40:15 +00:00
},
{
# Closed position
"symbol": "SOL/BUSD:BUSD",
"timestamp": None,
"datetime": None,
"initialMargin": 0.0,
"initialMarginPercentage": None,
"maintenanceMargin": 0.0,
"maintenanceMarginPercentage": 0.005,
"entryPrice": 0.0,
"notional": 0.0,
"leverage": 5.0,
"unrealizedPnl": 0.0,
"contracts": 0.0,
"contractSize": 1,
"marginRatio": None,
"liquidationPrice": 0.0,
"markPrice": 15.41,
"collateral": 0.0,
"marginType": "isolated",
2024-05-12 13:29:14 +00:00
"side": "short",
"percentage": None,
},
2022-02-23 06:40:15 +00:00
]
mocker.patch.multiple(
2023-03-01 19:14:15 +00:00
EXMS,
2024-05-12 13:29:14 +00:00
get_balances=MagicMock(
return_value={
"USDT": {"free": 900, "used": 100, "total": 1000},
}
),
fetch_positions=MagicMock(return_value=mock_result),
2022-02-23 06:40:15 +00:00
)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert len(freqtrade.wallets._wallets) == 1
assert len(freqtrade.wallets._positions) == 2
2024-05-12 13:29:14 +00:00
assert "USDT" in freqtrade.wallets._wallets
assert "ETH/USDT:USDT" in freqtrade.wallets._positions
2023-05-14 15:49:43 +00:00
assert freqtrade.wallets._last_wallet_refresh is not None
assert freqtrade.wallets.get_owned("ETH/USDT:USDT", "ETH") == 1000
assert freqtrade.wallets.get_owned("SOL/USDT:USDT", "SOL") == 0
2022-02-23 06:40:15 +00:00
# Remove ETH/USDT:USDT position
del mock_result[0]
freqtrade.wallets.update()
assert len(freqtrade.wallets._positions) == 1
2024-05-12 13:29:14 +00:00
assert "ETH/USDT:USDT" not in freqtrade.wallets._positions
2022-02-23 18:52:25 +00:00
2023-07-15 15:12:29 +00:00
def test_sync_wallet_dry(mocker, default_conf_usdt, fee):
2024-05-12 13:29:14 +00:00
default_conf_usdt["dry_run"] = True
2023-07-15 15:12:29 +00:00
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
assert len(freqtrade.wallets._wallets) == 1
assert len(freqtrade.wallets._positions) == 0
2024-05-12 13:29:14 +00:00
assert freqtrade.wallets.get_total("USDT") == 1000
2023-07-15 15:12:29 +00:00
create_mock_trades_usdt(fee, is_short=None)
freqtrade.wallets.update()
assert len(freqtrade.wallets._wallets) == 5
assert len(freqtrade.wallets._positions) == 0
bal = freqtrade.wallets.get_all_balances()
# NEO trade is not filled yet.
assert bal["NEO"].total == 0
2024-05-12 13:29:14 +00:00
assert bal["XRP"].total == 10
assert bal["LTC"].total == 2
2024-09-22 11:22:37 +00:00
usdt_bal = bal["USDT"]
assert usdt_bal.free == 922.74
assert usdt_bal.total == 942.74
assert usdt_bal.used == 20.0
# sum of used and free should be total.
assert usdt_bal.total == usdt_bal.free + usdt_bal.used
2024-05-12 13:29:14 +00:00
assert freqtrade.wallets.get_starting_balance() == default_conf_usdt["dry_run_wallet"]
total = freqtrade.wallets.get_total("LTC")
free = freqtrade.wallets.get_free("LTC")
used = freqtrade.wallets.get_used("LTC")
2024-09-22 11:22:37 +00:00
assert used != 0
2023-07-15 15:12:29 +00:00
assert free + used == total
2022-02-23 18:52:25 +00:00
def test_sync_wallet_futures_dry(mocker, default_conf, fee):
2024-05-12 13:29:14 +00:00
default_conf["dry_run"] = True
default_conf["trading_mode"] = "futures"
default_conf["margin_mode"] = "isolated"
2022-02-23 18:52:25 +00:00
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert len(freqtrade.wallets._wallets) == 1
assert len(freqtrade.wallets._positions) == 0
create_mock_trades(fee, is_short=None)
freqtrade.wallets.update()
assert len(freqtrade.wallets._wallets) == 1
assert len(freqtrade.wallets._positions) == 4
positions = freqtrade.wallets.get_all_positions()
2024-05-12 13:29:14 +00:00
assert positions["ETH/BTC"].side == "short"
assert positions["ETC/BTC"].side == "long"
assert positions["XRP/BTC"].side == "long"
assert positions["LTC/BTC"].side == "short"
assert freqtrade.wallets.get_starting_balance() == default_conf["dry_run_wallet"]
total = freqtrade.wallets.get_total("BTC")
free = freqtrade.wallets.get_free("BTC")
used = freqtrade.wallets.get_used("BTC")
assert free + used == total
2023-04-26 05:09:26 +00:00
def test_check_exit_amount(mocker, default_conf, fee):
freqtrade = get_patched_freqtradebot(mocker, default_conf)
update_mock = mocker.patch("freqtrade.wallets.Wallets.update")
total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=50.0)
2023-04-26 05:09:26 +00:00
create_mock_trades(fee, is_short=None)
trade = Trade.session.scalars(select(Trade)).first()
assert trade.amount == 50.0
2023-04-26 05:09:26 +00:00
assert freqtrade.wallets.check_exit_amount(trade) is True
assert update_mock.call_count == 0
assert total_mock.call_count == 1
update_mock.reset_mock()
# Reduce returned amount to below the trade amount - which should
# trigger a wallet update and return False, triggering "order refinding"
total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=40)
2023-04-26 05:09:26 +00:00
assert freqtrade.wallets.check_exit_amount(trade) is False
assert update_mock.call_count == 1
assert total_mock.call_count == 2
def test_check_exit_amount_futures(mocker, default_conf, fee):
2024-05-12 13:29:14 +00:00
default_conf["trading_mode"] = "futures"
default_conf["margin_mode"] = "isolated"
2023-04-26 05:09:26 +00:00
freqtrade = get_patched_freqtradebot(mocker, default_conf)
total_mock = mocker.patch("freqtrade.wallets.Wallets.get_total", return_value=50)
2023-04-26 05:09:26 +00:00
create_mock_trades(fee, is_short=None)
trade = Trade.session.scalars(select(Trade)).first()
2024-05-12 13:29:14 +00:00
trade.trading_mode = "futures"
assert trade.amount == 50
2023-04-26 05:09:26 +00:00
assert freqtrade.wallets.check_exit_amount(trade) is True
assert total_mock.call_count == 0
update_mock = mocker.patch("freqtrade.wallets.Wallets.update")
trade.amount = 150
# Reduce returned amount to below the trade amount - which should
# trigger a wallet update and return False, triggering "order refinding"
assert freqtrade.wallets.check_exit_amount(trade) is False
assert total_mock.call_count == 0
assert update_mock.call_count == 1