Merge pull request #6423 from samgermain/wallet-amt

Futures wallet amount
This commit is contained in:
Matthias 2022-02-26 15:55:28 +01:00 committed by GitHub
commit 79ddc9abaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 372 additions and 53 deletions

View File

@ -342,7 +342,7 @@ class Exchange:
def get_pair_base_currency(self, pair: str) -> str: def get_pair_base_currency(self, pair: str) -> str:
""" """
Return a pair's quote currency Return a pair's base currency
""" """
return self.markets.get(pair, {}).get('base', '') return self.markets.get(pair, {}).get('base', '')
@ -1167,6 +1167,22 @@ class Exchange:
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) from e raise OperationalException(e) from e
@retrier
def fetch_positions(self) -> List[Dict]:
if self._config['dry_run'] or self.trading_mode != TradingMode.FUTURES:
return []
try:
positions: List[Dict] = self._api.fetch_positions()
self._log_exchange_response('fetch_positions', positions)
return positions
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get positions due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
@retrier @retrier
def get_tickers(self, symbols: List[str] = None, cached: bool = False) -> Dict: def get_tickers(self, symbols: List[str] = None, cached: bool = False) -> Dict:
""" """

View File

@ -1335,11 +1335,13 @@ class FreqtradeBot(LoggingMixin):
""" """
# Update wallets to ensure amounts tied up in a stoploss is now free! # Update wallets to ensure amounts tied up in a stoploss is now free!
self.wallets.update() self.wallets.update()
if self.trading_mode == TradingMode.FUTURES:
return amount
trade_base_currency = self.exchange.get_pair_base_currency(pair) trade_base_currency = self.exchange.get_pair_base_currency(pair)
wallet_amount = self.wallets.get_free(trade_base_currency) wallet_amount = self.wallets.get_free(trade_base_currency)
logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}") logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}")
# TODO-lev: Get wallet amount + value of positions if wallet_amount >= amount:
if wallet_amount >= amount or self.trading_mode == TradingMode.FUTURES:
# A safe exit amount isn't needed for futures, you can just exit/close the position # A safe exit amount isn't needed for futures, you can just exit/close the position
return amount return amount
elif wallet_amount > amount * 0.98: elif wallet_amount > amount * 0.98:

View File

@ -385,6 +385,13 @@ class LocalTrade():
else: else:
return "sell" return "sell"
@property
def trade_direction(self) -> str:
if self.is_short:
return "short"
else:
return "long"
def __init__(self, **kwargs): def __init__(self, **kwargs):
for key in kwargs: for key in kwargs:
setattr(self, key, kwargs[key]) setattr(self, key, kwargs[key])

View File

@ -39,6 +39,11 @@ class Balance(BaseModel):
used: float used: float
est_stake: float est_stake: float
stake: str stake: str
# Starting with 2.x
side: str
leverage: float
is_position: bool
position: float
class Balances(BaseModel): class Balances(BaseModel):

View File

@ -30,6 +30,7 @@ from freqtrade.persistence.models import PairLock
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
from freqtrade.strategy.interface import SellCheckTuple from freqtrade.strategy.interface import SellCheckTuple
from freqtrade.wallets import PositionWallet, Wallet
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -566,7 +567,7 @@ class RPC:
def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict: def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict:
""" Returns current account balance per crypto """ """ Returns current account balance per crypto """
output = [] currencies = []
total = 0.0 total = 0.0
try: try:
tickers = self._freqtrade.exchange.get_tickers(cached=True) tickers = self._freqtrade.exchange.get_tickers(cached=True)
@ -577,7 +578,8 @@ class RPC:
starting_capital = self._freqtrade.wallets.get_starting_balance() starting_capital = self._freqtrade.wallets.get_starting_balance()
starting_cap_fiat = self._fiat_converter.convert_amount( starting_cap_fiat = self._fiat_converter.convert_amount(
starting_capital, stake_currency, fiat_display_currency) if self._fiat_converter else 0 starting_capital, stake_currency, fiat_display_currency) if self._fiat_converter else 0
coin: str
balance: Wallet
for coin, balance in self._freqtrade.wallets.get_all_balances().items(): for coin, balance in self._freqtrade.wallets.get_all_balances().items():
if not balance.total: if not balance.total:
continue continue
@ -598,13 +600,34 @@ class RPC:
logger.warning(f" Could not get rate for pair {coin}.") logger.warning(f" Could not get rate for pair {coin}.")
continue continue
total = total + (est_stake or 0) total = total + (est_stake or 0)
output.append({ currencies.append({
'currency': coin, 'currency': coin,
# TODO: The below can be simplified if we don't assign None to values.
'free': balance.free if balance.free is not None else 0, 'free': balance.free if balance.free is not None else 0,
'balance': balance.total if balance.total is not None else 0, 'balance': balance.total if balance.total is not None else 0,
'used': balance.used if balance.used is not None else 0, 'used': balance.used if balance.used is not None else 0,
'est_stake': est_stake or 0, 'est_stake': est_stake or 0,
'stake': stake_currency, 'stake': stake_currency,
'side': 'long',
'leverage': 1,
'position': 0,
'is_position': False,
})
symbol: str
position: PositionWallet
for symbol, position in self._freqtrade.wallets.get_all_positions().items():
currencies.append({
'currency': symbol,
'free': 0,
'balance': 0,
'used': 0,
'position': position.position,
'est_stake': position.collateral,
'stake': stake_currency,
'leverage': position.leverage,
'side': position.side,
'is_position': True
}) })
value = self._fiat_converter.convert_amount( value = self._fiat_converter.convert_amount(
@ -616,7 +639,7 @@ class RPC:
starting_cap_fiat_ratio = (value / starting_cap_fiat) - 1 if starting_cap_fiat else 0.0 starting_cap_fiat_ratio = (value / starting_cap_fiat) - 1 if starting_cap_fiat else 0.0
return { return {
'currencies': output, 'currencies': currencies,
'total': total, 'total': total,
'symbol': fiat_display_currency, 'symbol': fiat_display_currency,
'value': value, 'value': value,

View File

@ -827,13 +827,21 @@ class Telegram(RPCHandler):
for curr in result['currencies']: for curr in result['currencies']:
curr_output = '' curr_output = ''
if curr['est_stake'] > balance_dust_level: if curr['est_stake'] > balance_dust_level:
curr_output = ( if curr['is_position']:
f"*{curr['currency']}:*\n" curr_output = (
f"\t`Available: {curr['free']:.8f}`\n" f"*{curr['currency']}:*\n"
f"\t`Balance: {curr['balance']:.8f}`\n" f"\t`{curr['side']}: {curr['position']:.8f}`\n"
f"\t`Pending: {curr['used']:.8f}`\n" f"\t`Leverage: {curr['leverage']:.1f}`\n"
f"\t`Est. {curr['stake']}: " f"\t`Est. {curr['stake']}: "
f"{round_coin_value(curr['est_stake'], curr['stake'], False)}`\n") f"{round_coin_value(curr['est_stake'], curr['stake'], False)}`\n")
else:
curr_output = (
f"*{curr['currency']}:*\n"
f"\t`Available: {curr['free']:.8f}`\n"
f"\t`Balance: {curr['balance']:.8f}`\n"
f"\t`Pending: {curr['used']:.8f}`\n"
f"\t`Est. {curr['stake']}: "
f"{round_coin_value(curr['est_stake'], curr['stake'], False)}`\n")
elif curr['est_stake'] <= balance_dust_level: elif curr['est_stake'] <= balance_dust_level:
total_dust_balance += curr['est_stake'] total_dust_balance += curr['est_stake']
total_dust_currencies += 1 total_dust_currencies += 1

View File

@ -3,12 +3,12 @@
import logging import logging
from copy import deepcopy from copy import deepcopy
from typing import Any, Dict, NamedTuple, Optional from typing import Dict, NamedTuple, Optional
import arrow import arrow
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
from freqtrade.enums import RunMode from freqtrade.enums import RunMode, TradingMode
from freqtrade.exceptions import DependencyException from freqtrade.exceptions import DependencyException
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade.persistence import LocalTrade, Trade from freqtrade.persistence import LocalTrade, Trade
@ -25,6 +25,14 @@ class Wallet(NamedTuple):
total: float = 0 total: float = 0
class PositionWallet(NamedTuple):
symbol: str
position: float = 0
leverage: float = 0
collateral: float = 0
side: str = 'long'
class Wallets: class Wallets:
def __init__(self, config: dict, exchange: Exchange, log: bool = True) -> None: def __init__(self, config: dict, exchange: Exchange, log: bool = True) -> None:
@ -32,6 +40,7 @@ class Wallets:
self._log = log self._log = log
self._exchange = exchange self._exchange = exchange
self._wallets: Dict[str, Wallet] = {} self._wallets: Dict[str, Wallet] = {}
self._positions: Dict[str, PositionWallet] = {}
self.start_cap = config['dry_run_wallet'] self.start_cap = config['dry_run_wallet']
self._last_wallet_refresh = 0 self._last_wallet_refresh = 0
self.update() self.update()
@ -66,6 +75,7 @@ class Wallets:
""" """
# Recreate _wallets to reset closed trade balances # Recreate _wallets to reset closed trade balances
_wallets = {} _wallets = {}
_positions = {}
open_trades = Trade.get_trades_proxy(is_open=True) open_trades = Trade.get_trades_proxy(is_open=True)
# If not backtesting... # If not backtesting...
# TODO: potentially remove the ._log workaround to determine backtest mode. # TODO: potentially remove the ._log workaround to determine backtest mode.
@ -74,24 +84,46 @@ class Wallets:
else: else:
tot_profit = LocalTrade.total_profit tot_profit = LocalTrade.total_profit
tot_in_trades = sum(trade.stake_amount for trade in open_trades) tot_in_trades = sum(trade.stake_amount for trade in open_trades)
used_stake = 0.0
if self._config.get('trading_mode', 'spot') != TradingMode.FUTURES:
current_stake = self.start_cap + tot_profit - tot_in_trades
total_stake = current_stake
for trade in open_trades:
curr = self._exchange.get_pair_base_currency(trade.pair)
_wallets[curr] = Wallet(
curr,
trade.amount,
0,
trade.amount
)
else:
tot_in_trades = 0
for position in open_trades:
# size = self._exchange._contracts_to_amount(position.pair, position['contracts'])
size = position.amount
# TODO-lev: stake_amount in real trades does not include the leverage ...
collateral = position.stake_amount / position.leverage
leverage = position.leverage
tot_in_trades -= collateral
_positions[position.pair] = PositionWallet(
position.pair, position=size,
leverage=leverage,
collateral=collateral,
side=position.trade_direction
)
current_stake = self.start_cap + tot_profit
used_stake = tot_in_trades
total_stake = current_stake - tot_in_trades
current_stake = self.start_cap + tot_profit - tot_in_trades
_wallets[self._config['stake_currency']] = Wallet( _wallets[self._config['stake_currency']] = Wallet(
self._config['stake_currency'], currency=self._config['stake_currency'],
current_stake, free=current_stake,
0, used=used_stake,
current_stake total=total_stake
) )
for trade in open_trades:
curr = self._exchange.get_pair_base_currency(trade.pair)
_wallets[curr] = Wallet(
curr,
trade.amount,
0,
trade.amount
)
self._wallets = _wallets self._wallets = _wallets
self._positions = _positions
def _update_live(self) -> None: def _update_live(self) -> None:
balances = self._exchange.get_balances() balances = self._exchange.get_balances()
@ -109,6 +141,23 @@ class Wallets:
if currency not in balances: if currency not in balances:
del self._wallets[currency] del self._wallets[currency]
positions = self._exchange.fetch_positions()
self._positions = {}
for position in positions:
symbol = position['symbol']
if position['side'] is None or position['collateral'] == 0.0:
# Position is not open ...
continue
size = self._exchange._contracts_to_amount(symbol, position['contracts'])
collateral = position['collateral']
leverage = position['leverage']
self._positions[symbol] = PositionWallet(
symbol, position=size,
leverage=leverage,
collateral=collateral,
side=position['side']
)
def update(self, require_update: bool = True) -> None: def update(self, require_update: bool = True) -> None:
""" """
Updates wallets from the configured version. Updates wallets from the configured version.
@ -126,9 +175,12 @@ class Wallets:
logger.info('Wallets synced.') logger.info('Wallets synced.')
self._last_wallet_refresh = arrow.utcnow().int_timestamp self._last_wallet_refresh = arrow.utcnow().int_timestamp
def get_all_balances(self) -> Dict[str, Any]: def get_all_balances(self) -> Dict[str, Wallet]:
return self._wallets return self._wallets
def get_all_positions(self) -> Dict[str, PositionWallet]:
return self._positions
def get_starting_balance(self) -> float: def get_starting_balance(self) -> float:
""" """
Retrieves starting balance - based on either available capital, Retrieves starting balance - based on either available capital,

View File

@ -1547,6 +1547,27 @@ def test_get_balances_prod(default_conf, mocker, exchange_name):
"get_balances", "fetch_balance") "get_balances", "fetch_balance")
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_fetch_positions(default_conf, mocker, exchange_name):
mocker.patch('freqtrade.exchange.Exchange.validate_trading_mode_and_margin_mode')
api_mock = MagicMock()
api_mock.fetch_positions = MagicMock(return_value=[
{'symbol': 'ETH/USDT:USDT', 'leverage': 5},
{'symbol': 'XRP/USDT:USDT', 'leverage': 5},
])
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
assert exchange.fetch_positions() == []
default_conf['dry_run'] = False
default_conf['trading_mode'] = 'futures'
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
res = exchange.fetch_positions()
assert len(res) == 2
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
"fetch_positions", "fetch_positions")
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_tickers(default_conf, mocker, exchange_name): def test_get_tickers(default_conf, mocker, exchange_name):
api_mock = MagicMock() api_mock = MagicMock()

View File

@ -603,6 +603,30 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
'used': 5.0, 'used': 5.0,
} }
} }
mock_pos = [
{
"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",
"side": 'short',
"percentage": None
}
]
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.fiat_convert.CoinGeckoAPI', 'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
@ -612,12 +636,15 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
validate_trading_mode_and_margin_mode=MagicMock(),
get_balances=MagicMock(return_value=mock_balance), get_balances=MagicMock(return_value=mock_balance),
fetch_positions=MagicMock(return_value=mock_pos),
get_tickers=tickers, get_tickers=tickers,
get_valid_pair_combination=MagicMock( get_valid_pair_combination=MagicMock(
side_effect=lambda a, b: f"{b}/{a}" if a == "USDT" else f"{a}/{b}") side_effect=lambda a, b: f"{b}/{a}" if a == "USDT" else f"{a}/{b}")
) )
default_conf['dry_run'] = False default_conf['dry_run'] = False
default_conf['trading_mode'] = 'futures'
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot) patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
@ -630,28 +657,55 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
assert tickers.call_args_list[0][1]['cached'] is True assert tickers.call_args_list[0][1]['cached'] is True
assert 'USD' == result['symbol'] assert 'USD' == result['symbol']
assert result['currencies'] == [ assert result['currencies'] == [
{'currency': 'BTC', {
'free': 10.0, 'currency': 'BTC',
'balance': 12.0, 'free': 10.0,
'used': 2.0, 'balance': 12.0,
'est_stake': 12.0, 'used': 2.0,
'stake': 'BTC', 'est_stake': 12.0,
}, 'stake': 'BTC',
{'free': 1.0, 'is_position': False,
'balance': 5.0, 'leverage': 1.0,
'currency': 'ETH', 'position': 0.0,
'est_stake': 0.30794, 'side': 'long',
'used': 4.0, },
'stake': 'BTC', {
'free': 1.0,
'balance': 5.0,
'currency': 'ETH',
'est_stake': 0.30794,
'used': 4.0,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
}, },
{'free': 5.0, {
'balance': 10.0, 'free': 5.0,
'currency': 'USDT', 'balance': 10.0,
'est_stake': 0.0011563153318162476, 'currency': 'USDT',
'used': 5.0, 'est_stake': 0.0011563153318162476,
'stake': 'BTC', 'used': 5.0,
} 'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
},
{
'free': 0.0,
'balance': 0.0,
'currency': 'ETH/USDT:USDT',
'est_stake': 20,
'used': 0,
'stake': 'BTC',
'is_position': True,
'leverage': 5.0,
'position': 1000.0,
'side': 'short',
}
] ]
assert result['total'] == 12.309096315331816 assert result['total'] == 12.309096315331816

View File

@ -453,6 +453,10 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers):
'used': 0.0, 'used': 0.0,
'est_stake': 12.0, 'est_stake': 12.0,
'stake': 'BTC', 'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
} }
assert 'starting_capital' in response assert 'starting_capital' in response
assert 'starting_capital_fiat' in response assert 'starting_capital_fiat' in response

View File

@ -905,6 +905,10 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None
'balance': i, 'balance': i,
'est_stake': 1, 'est_stake': 1,
'stake': 'BTC', 'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
}) })
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_balance', return_value={ mocker.patch('freqtrade.rpc.rpc.RPC._rpc_balance', return_value={
'currencies': balances, 'currencies': balances,

View File

@ -95,6 +95,7 @@ def test_enter_exit_side(fee, is_short):
) )
assert trade.enter_side == enter_side assert trade.enter_side == enter_side
assert trade.exit_side == exit_side assert trade.exit_side == exit_side
assert trade.trade_direction == 'short' if is_short else 'long'
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")

View File

@ -6,7 +6,7 @@ import pytest
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
from freqtrade.exceptions import DependencyException from freqtrade.exceptions import DependencyException
from tests.conftest import get_patched_freqtradebot, patch_wallet from tests.conftest import create_mock_trades, get_patched_freqtradebot, patch_wallet
def test_sync_wallet_at_boot(mocker, default_conf): def test_sync_wallet_at_boot(mocker, default_conf):
@ -234,3 +234,125 @@ def test_get_starting_balance(mocker, default_conf, available_capital, closed_pr
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert freqtrade.wallets.get_starting_balance() == expected assert freqtrade.wallets.get_starting_balance() == expected
def test_sync_wallet_futures_live(mocker, default_conf):
default_conf['dry_run'] = False
default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated'
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",
"side": 'short',
"percentage": None
},
{
"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",
"side": 'short',
"percentage": None
},
{
# 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",
"side": 'short',
"percentage": None
}
]
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value={
"USDT": {
"free": 900,
"used": 100,
"total": 1000
},
}),
fetch_positions=MagicMock(return_value=mock_result)
)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert len(freqtrade.wallets._wallets) == 1
assert len(freqtrade.wallets._positions) == 2
assert 'USDT' in freqtrade.wallets._wallets
assert 'ETH/USDT:USDT' in freqtrade.wallets._positions
assert freqtrade.wallets._last_wallet_refresh > 0
# Remove ETH/USDT:USDT position
del mock_result[0]
freqtrade.wallets.update()
assert len(freqtrade.wallets._positions) == 1
assert 'ETH/USDT:USDT' not in freqtrade.wallets._positions
def test_sync_wallet_futures_dry(mocker, default_conf, fee):
default_conf['dry_run'] = True
default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated'
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()
positions['ETH/BTC'].side == 'short'
positions['ETC/BTC'].side == 'long'
positions['XRP/BTC'].side == 'long'
positions['LTC/BTC'].side == 'short'