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
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:
"""
Return a pair's quote currency
Return a pair's base currency
"""
return self.markets.get(pair, {}).get('base', '')
@@ -1167,6 +1167,22 @@ class Exchange:
except ccxt.BaseError as 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
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!
self.wallets.update()
if self.trading_mode == TradingMode.FUTURES:
return amount
trade_base_currency = self.exchange.get_pair_base_currency(pair)
wallet_amount = self.wallets.get_free(trade_base_currency)
logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}")
# TODO-lev: Get wallet amount + value of positions
if wallet_amount >= amount or self.trading_mode == TradingMode.FUTURES:
if wallet_amount >= amount:
# A safe exit amount isn't needed for futures, you can just exit/close the position
return amount
elif wallet_amount > amount * 0.98:

View File

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

View File

@@ -39,6 +39,11 @@ class Balance(BaseModel):
used: float
est_stake: float
stake: str
# Starting with 2.x
side: str
leverage: float
is_position: bool
position: float
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.rpc.fiat_convert import CryptoToFiatConverter
from freqtrade.strategy.interface import SellCheckTuple
from freqtrade.wallets import PositionWallet, Wallet
logger = logging.getLogger(__name__)
@@ -566,7 +567,7 @@ class RPC:
def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict:
""" Returns current account balance per crypto """
output = []
currencies = []
total = 0.0
try:
tickers = self._freqtrade.exchange.get_tickers(cached=True)
@@ -577,7 +578,8 @@ class RPC:
starting_capital = self._freqtrade.wallets.get_starting_balance()
starting_cap_fiat = self._fiat_converter.convert_amount(
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():
if not balance.total:
continue
@@ -598,13 +600,34 @@ class RPC:
logger.warning(f" Could not get rate for pair {coin}.")
continue
total = total + (est_stake or 0)
output.append({
currencies.append({
'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,
'balance': balance.total if balance.total is not None else 0,
'used': balance.used if balance.used is not None else 0,
'est_stake': est_stake or 0,
'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(
@@ -616,7 +639,7 @@ class RPC:
starting_cap_fiat_ratio = (value / starting_cap_fiat) - 1 if starting_cap_fiat else 0.0
return {
'currencies': output,
'currencies': currencies,
'total': total,
'symbol': fiat_display_currency,
'value': value,

View File

@@ -827,13 +827,21 @@ class Telegram(RPCHandler):
for curr in result['currencies']:
curr_output = ''
if curr['est_stake'] > balance_dust_level:
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")
if curr['is_position']:
curr_output = (
f"*{curr['currency']}:*\n"
f"\t`{curr['side']}: {curr['position']:.8f}`\n"
f"\t`Leverage: {curr['leverage']:.1f}`\n"
f"\t`Est. {curr['stake']}: "
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:
total_dust_balance += curr['est_stake']
total_dust_currencies += 1

View File

@@ -3,12 +3,12 @@
import logging
from copy import deepcopy
from typing import Any, Dict, NamedTuple, Optional
from typing import Dict, NamedTuple, Optional
import arrow
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.exchange import Exchange
from freqtrade.persistence import LocalTrade, Trade
@@ -25,6 +25,14 @@ class Wallet(NamedTuple):
total: float = 0
class PositionWallet(NamedTuple):
symbol: str
position: float = 0
leverage: float = 0
collateral: float = 0
side: str = 'long'
class Wallets:
def __init__(self, config: dict, exchange: Exchange, log: bool = True) -> None:
@@ -32,6 +40,7 @@ class Wallets:
self._log = log
self._exchange = exchange
self._wallets: Dict[str, Wallet] = {}
self._positions: Dict[str, PositionWallet] = {}
self.start_cap = config['dry_run_wallet']
self._last_wallet_refresh = 0
self.update()
@@ -66,6 +75,7 @@ class Wallets:
"""
# Recreate _wallets to reset closed trade balances
_wallets = {}
_positions = {}
open_trades = Trade.get_trades_proxy(is_open=True)
# If not backtesting...
# TODO: potentially remove the ._log workaround to determine backtest mode.
@@ -74,24 +84,46 @@ class Wallets:
else:
tot_profit = LocalTrade.total_profit
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(
self._config['stake_currency'],
current_stake,
0,
current_stake
currency=self._config['stake_currency'],
free=current_stake,
used=used_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._positions = _positions
def _update_live(self) -> None:
balances = self._exchange.get_balances()
@@ -109,6 +141,23 @@ class Wallets:
if currency not in balances:
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:
"""
Updates wallets from the configured version.
@@ -126,9 +175,12 @@ class Wallets:
logger.info('Wallets synced.')
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
def get_all_positions(self) -> Dict[str, PositionWallet]:
return self._positions
def get_starting_balance(self) -> float:
"""
Retrieves starting balance - based on either available capital,