Skip to content

Commit

Permalink
Move get_trade_stake_amount to wallets
Browse files Browse the repository at this point in the history
this way it can be easier used by other functions
  • Loading branch information
xmatthias committed Feb 3, 2021
1 parent 6c87c49 commit b8cb394
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 93 deletions.
82 changes: 3 additions & 79 deletions freqtrade/freqtradebot.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ def _refresh_active_whitelist(self, trades: List[Trade] = []) -> List[str]:
_whitelist.extend([trade.pair for trade in trades if trade.pair not in _whitelist])
return _whitelist

def get_free_open_trades(self):
def get_free_open_trades(self) -> int:
"""
Return the number of free open trades slots or 0 if
max number of open trades reached
Expand Down Expand Up @@ -439,83 +439,6 @@ def get_buy_rate(self, pair: str, refresh: bool) -> float:

return used_rate

def get_trade_stake_amount(self, pair: str) -> float:
"""
Calculate stake amount for the trade
:return: float: Stake amount
:raise: DependencyException if the available stake amount is too low
"""
stake_amount: float
# Ensure wallets are uptodate.
self.wallets.update()

if self.edge:
stake_amount = self.edge.stake_amount(
pair,
self.wallets.get_free(self.config['stake_currency']),
self.wallets.get_total(self.config['stake_currency']),
Trade.total_open_trades_stakes()
)
else:
stake_amount = self.config['stake_amount']
if stake_amount == constants.UNLIMITED_STAKE_AMOUNT:
stake_amount = self._calculate_unlimited_stake_amount()

return self._check_available_stake_amount(stake_amount)

def _get_available_stake_amount(self) -> float:
"""
Return the total currently available balance in stake currency,
respecting tradable_balance_ratio.
Calculated as
<open_trade stakes> + free amount ) * tradable_balance_ratio - <open_trade stakes>
"""
val_tied_up = Trade.total_open_trades_stakes()

# Ensure <tradable_balance_ratio>% is used from the overall balance
# Otherwise we'd risk lowering stakes with each open trade.
# (tied up + current free) * ratio) - tied up
available_amount = ((val_tied_up + self.wallets.get_free(self.config['stake_currency'])) *
self.config['tradable_balance_ratio']) - val_tied_up
return available_amount

def _calculate_unlimited_stake_amount(self) -> float:
"""
Calculate stake amount for "unlimited" stake amount
:return: 0 if max number of trades reached, else stake_amount to use.
"""
free_open_trades = self.get_free_open_trades()
if not free_open_trades:
return 0

available_amount = self._get_available_stake_amount()

return available_amount / free_open_trades

def _check_available_stake_amount(self, stake_amount: float) -> float:
"""
Check if stake amount can be fulfilled with the available balance
for the stake currency
:return: float: Stake amount
"""
available_amount = self._get_available_stake_amount()

if self.config['amend_last_stake_amount']:
# Remaining amount needs to be at least stake_amount * last_stake_amount_min_ratio
# Otherwise the remaining amount is too low to trade.
if available_amount > (stake_amount * self.config['last_stake_amount_min_ratio']):
stake_amount = min(stake_amount, available_amount)
else:
stake_amount = 0

if available_amount < stake_amount:
raise DependencyException(
f"Available balance ({available_amount} {self.config['stake_currency']}) is "
f"lower than stake amount ({stake_amount} {self.config['stake_currency']})"
)

return stake_amount

def create_trade(self, pair: str) -> bool:
"""
Check the implemented trading strategy for buy signals.
Expand Down Expand Up @@ -549,7 +472,8 @@ def create_trade(self, pair: str) -> bool:
(buy, sell) = self.strategy.get_signal(pair, self.strategy.timeframe, analyzed_df)

if buy and not sell:
stake_amount = self.get_trade_stake_amount(pair)
stake_amount = self.wallets.get_trade_stake_amount(pair, self.get_free_open_trades(),
self.edge)
if not stake_amount:
logger.debug(f"Stake amount is 0, ignoring possible trade for {pair}.")
return False
Expand Down
3 changes: 2 additions & 1 deletion freqtrade/rpc/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,8 @@ def _rpc_forcebuy(self, pair: str, price: Optional[float]) -> Optional[Trade]:
raise RPCException(f'position for {pair} already open - id: {trade.id}')

# gen stake amount
stakeamount = self._freqtrade.get_trade_stake_amount(pair)
stakeamount = self._freqtrade.wallets.get_trade_stake_amount(
pair, self._freqtrade.get_free_open_trades())

# execute buy
if self._freqtrade.execute_buy(pair, stakeamount, price):
Expand Down
78 changes: 78 additions & 0 deletions freqtrade/wallets.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import arrow

from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
from freqtrade.exceptions import DependencyException
from freqtrade.exchange import Exchange
from freqtrade.persistence import Trade

Expand Down Expand Up @@ -118,3 +120,79 @@ def update(self, require_update: bool = True) -> None:

def get_all_balances(self) -> Dict[str, Any]:
return self._wallets

def _get_available_stake_amount(self) -> float:
"""
Return the total currently available balance in stake currency,
respecting tradable_balance_ratio.
Calculated as
(<open_trade stakes> + free amount ) * tradable_balance_ratio - <open_trade stakes>
"""
val_tied_up = Trade.total_open_trades_stakes()

# Ensure <tradable_balance_ratio>% is used from the overall balance
# Otherwise we'd risk lowering stakes with each open trade.
# (tied up + current free) * ratio) - tied up
available_amount = ((val_tied_up + self.get_free(self._config['stake_currency'])) *
self._config['tradable_balance_ratio']) - val_tied_up
return available_amount

def _calculate_unlimited_stake_amount(self, free_open_trades: int) -> float:
"""
Calculate stake amount for "unlimited" stake amount
:return: 0 if max number of trades reached, else stake_amount to use.
"""
if not free_open_trades:
return 0

available_amount = self._get_available_stake_amount()

return available_amount / free_open_trades

def _check_available_stake_amount(self, stake_amount: float) -> float:
"""
Check if stake amount can be fulfilled with the available balance
for the stake currency
:return: float: Stake amount
"""
available_amount = self._get_available_stake_amount()

if self._config['amend_last_stake_amount']:
# Remaining amount needs to be at least stake_amount * last_stake_amount_min_ratio
# Otherwise the remaining amount is too low to trade.
if available_amount > (stake_amount * self._config['last_stake_amount_min_ratio']):
stake_amount = min(stake_amount, available_amount)
else:
stake_amount = 0

if available_amount < stake_amount:
raise DependencyException(
f"Available balance ({available_amount} {self._config['stake_currency']}) is "
f"lower than stake amount ({stake_amount} {self._config['stake_currency']})"
)

return stake_amount

def get_trade_stake_amount(self, pair: str, free_open_trades: int, edge=None) -> float:
"""
Calculate stake amount for the trade
:return: float: Stake amount
:raise: DependencyException if the available stake amount is too low
"""
stake_amount: float
# Ensure wallets are uptodate.
self.update()

if edge:
stake_amount = edge.stake_amount(
pair,
self.get_free(self._config['stake_currency']),
self.get_total(self._config['stake_currency']),
Trade.total_open_trades_stakes()
)
else:
stake_amount = self._config['stake_amount']
if stake_amount == UNLIMITED_STAKE_AMOUNT:
stake_amount = self._calculate_unlimited_stake_amount(free_open_trades)

return self._check_available_stake_amount(stake_amount)
28 changes: 17 additions & 11 deletions tests/test_freqtradebot.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ def test_get_trade_stake_amount(default_conf, ticker, mocker) -> None:

freqtrade = FreqtradeBot(default_conf)

result = freqtrade.get_trade_stake_amount('ETH/BTC')
result = freqtrade.wallets.get_trade_stake_amount(
'ETH/BTC', freqtrade.get_free_open_trades())
assert result == default_conf['stake_amount']


Expand Down Expand Up @@ -194,12 +195,14 @@ def test_check_available_stake_amount(default_conf, ticker, mocker, fee, limit_b

if expected[i] is not None:
limit_buy_order_open['id'] = str(i)
result = freqtrade.get_trade_stake_amount('ETH/BTC')
result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC',
freqtrade.get_free_open_trades())
assert pytest.approx(result) == expected[i]
freqtrade.execute_buy('ETH/BTC', result)
else:
with pytest.raises(DependencyException):
freqtrade.get_trade_stake_amount('ETH/BTC')
freqtrade.wallets.get_trade_stake_amount('ETH/BTC',
freqtrade.get_free_open_trades())


def test_get_trade_stake_amount_no_stake_amount(default_conf, mocker) -> None:
Expand All @@ -210,7 +213,7 @@ def test_get_trade_stake_amount_no_stake_amount(default_conf, mocker) -> None:
patch_get_signal(freqtrade)

with pytest.raises(DependencyException, match=r'.*stake amount.*'):
freqtrade.get_trade_stake_amount('ETH/BTC')
freqtrade.wallets.get_trade_stake_amount('ETH/BTC', freqtrade.get_free_open_trades())


@pytest.mark.parametrize("balance_ratio,result1", [
Expand Down Expand Up @@ -239,25 +242,25 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r
patch_get_signal(freqtrade)

# no open trades, order amount should be 'balance / max_open_trades'
result = freqtrade.get_trade_stake_amount('ETH/BTC')
result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC', freqtrade.get_free_open_trades())
assert result == result1

# create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)'
freqtrade.execute_buy('ETH/BTC', result)

result = freqtrade.get_trade_stake_amount('LTC/BTC')
result = freqtrade.wallets.get_trade_stake_amount('LTC/BTC', freqtrade.get_free_open_trades())
assert result == result1

# create 2 trades, order amount should be None
freqtrade.execute_buy('LTC/BTC', result)

result = freqtrade.get_trade_stake_amount('XRP/BTC')
result = freqtrade.wallets.get_trade_stake_amount('XRP/BTC', freqtrade.get_free_open_trades())
assert result == 0

# set max_open_trades = None, so do not trade
conf['max_open_trades'] = 0
freqtrade = FreqtradeBot(conf)
result = freqtrade.get_trade_stake_amount('NEO/BTC')
result = freqtrade.wallets.get_trade_stake_amount('NEO/BTC', freqtrade.get_free_open_trades())
assert result == 0


Expand All @@ -283,8 +286,10 @@ def test_edge_overrides_stake_amount(mocker, edge_conf) -> None:
edge_conf['dry_run_wallet'] = 999.9
freqtrade = FreqtradeBot(edge_conf)

assert freqtrade.get_trade_stake_amount('NEO/BTC') == (999.9 * 0.5 * 0.01) / 0.20
assert freqtrade.get_trade_stake_amount('LTC/BTC') == (999.9 * 0.5 * 0.01) / 0.21
assert freqtrade.wallets.get_trade_stake_amount(
'NEO/BTC', freqtrade.get_free_open_trades(), freqtrade.edge) == (999.9 * 0.5 * 0.01) / 0.20
assert freqtrade.wallets.get_trade_stake_amount(
'LTC/BTC', freqtrade.get_free_open_trades(), freqtrade.edge) == (999.9 * 0.5 * 0.01) / 0.21


def test_edge_overrides_stoploss(limit_buy_order, fee, caplog, mocker, edge_conf) -> None:
Expand Down Expand Up @@ -500,7 +505,8 @@ def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order_open,
patch_get_signal(freqtrade)

assert not freqtrade.create_trade('ETH/BTC')
assert freqtrade.get_trade_stake_amount('ETH/BTC') == 0
assert freqtrade.wallets.get_trade_stake_amount('ETH/BTC', freqtrade.get_free_open_trades(),
freqtrade.edge) == 0


def test_enter_positions_no_pairs_left(default_conf, ticker, limit_buy_order_open, fee,
Expand Down
6 changes: 4 additions & 2 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc

trades = Trade.query.all()
assert len(trades) == 4
assert freqtrade.get_trade_stake_amount('XRP/BTC') == result1
assert freqtrade.wallets.get_trade_stake_amount(
'XRP/BTC', freqtrade.get_free_open_trades()) == result1

rpc._rpc_forcebuy('TKN/BTC', None)

Expand All @@ -199,7 +200,8 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
# One trade sold
assert len(trades) == 4
# stake-amount should now be reduced, since one trade was sold at a loss.
assert freqtrade.get_trade_stake_amount('XRP/BTC') < result1
assert freqtrade.wallets.get_trade_stake_amount(
'XRP/BTC', freqtrade.get_free_open_trades()) < result1
# Validate that balance of sold trade is not in dry-run balances anymore.
bals2 = freqtrade.wallets.get_all_balances()
assert bals != bals2
Expand Down

0 comments on commit b8cb394

Please sign in to comment.