Merge branch 'handleNotEnoughFundsException' into develop-berlinguyinca
# Conflicts: # freqtrade/tests/testdata/BTC_ADA-5.json # freqtrade/tests/testdata/BTC_DASH-5.json # freqtrade/tests/testdata/BTC_ETC-5.json # freqtrade/tests/testdata/BTC_ETH-5.json # freqtrade/tests/testdata/BTC_LTC-5.json # freqtrade/tests/testdata/BTC_NXT-5.json # freqtrade/tests/testdata/BTC_POWR-5.json # freqtrade/tests/testdata/BTC_XLM-5.json # freqtrade/tests/testdata/BTC_XMR-5.json # freqtrade/tests/testdata/BTC_ZEC-5.json
This commit is contained in:
commit
5d59cd4d51
@ -14,3 +14,11 @@ class OperationalException(BaseException):
|
|||||||
Requires manual intervention.
|
Requires manual intervention.
|
||||||
This happens when an exchange returns an unexpected error during runtime.
|
This happens when an exchange returns an unexpected error during runtime.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class NotEnoughFundsExeption(BaseException):
|
||||||
|
"""
|
||||||
|
This happens when the exchange reports that not enough funds where available.
|
||||||
|
We do not want to stop the bot in this case and just keep it going and suppress
|
||||||
|
this message
|
||||||
|
"""
|
||||||
|
@ -5,7 +5,7 @@ from bittrex.bittrex import API_V1_1, API_V2_0
|
|||||||
from bittrex.bittrex import Bittrex as _Bittrex
|
from bittrex.bittrex import Bittrex as _Bittrex
|
||||||
from requests.exceptions import ContentDecodingError
|
from requests.exceptions import ContentDecodingError
|
||||||
|
|
||||||
from freqtrade import OperationalException
|
from freqtrade import OperationalException, NotEnoughFundsExeption
|
||||||
from freqtrade.exchange.interface import Exchange
|
from freqtrade.exchange.interface import Exchange
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -63,22 +63,37 @@ class Bittrex(Exchange):
|
|||||||
data = _API.buy_limit(pair.replace('_', '-'), amount, rate)
|
data = _API.buy_limit(pair.replace('_', '-'), amount, rate)
|
||||||
if not data['success']:
|
if not data['success']:
|
||||||
Bittrex._validate_response(data)
|
Bittrex._validate_response(data)
|
||||||
raise OperationalException('{message} params=({pair}, {rate}, {amount})'.format(
|
|
||||||
message=data['message'],
|
if data['message'] == "INSUFFICIENT_FUNDS":
|
||||||
pair=pair,
|
raise NotEnoughFundsExeption('{message} params=({pair}, {rate}, {amount})'.format(
|
||||||
rate=rate,
|
message=data['message'],
|
||||||
amount=amount))
|
pair=pair,
|
||||||
|
rate=rate,
|
||||||
|
amount=amount))
|
||||||
|
else:
|
||||||
|
raise OperationalException('{message} params=({pair}, {rate}, {amount})'.format(
|
||||||
|
message=data['message'],
|
||||||
|
pair=pair,
|
||||||
|
rate=rate,
|
||||||
|
amount=amount))
|
||||||
return data['result']['uuid']
|
return data['result']['uuid']
|
||||||
|
|
||||||
def sell(self, pair: str, rate: float, amount: float) -> str:
|
def sell(self, pair: str, rate: float, amount: float) -> str:
|
||||||
data = _API.sell_limit(pair.replace('_', '-'), amount, rate)
|
data = _API.sell_limit(pair.replace('_', '-'), amount, rate)
|
||||||
if not data['success']:
|
if not data['success']:
|
||||||
Bittrex._validate_response(data)
|
Bittrex._validate_response(data)
|
||||||
raise OperationalException('{message} params=({pair}, {rate}, {amount})'.format(
|
if data['message'] == "INSUFFICIENT_FUNDS":
|
||||||
message=data['message'],
|
raise NotEnoughFundsExeption('{message} params=({pair}, {rate}, {amount})'.format(
|
||||||
pair=pair,
|
message=data['message'],
|
||||||
rate=rate,
|
pair=pair,
|
||||||
amount=amount))
|
rate=rate,
|
||||||
|
amount=amount))
|
||||||
|
else:
|
||||||
|
raise OperationalException('{message} params=({pair}, {rate}, {amount})'.format(
|
||||||
|
message=data['message'],
|
||||||
|
pair=pair,
|
||||||
|
rate=rate,
|
||||||
|
amount=amount))
|
||||||
return data['result']['uuid']
|
return data['result']['uuid']
|
||||||
|
|
||||||
def get_balance(self, currency: str) -> float:
|
def get_balance(self, currency: str) -> float:
|
||||||
|
@ -15,8 +15,8 @@ import requests
|
|||||||
from cachetools import cached, TTLCache
|
from cachetools import cached, TTLCache
|
||||||
|
|
||||||
from freqtrade import (
|
from freqtrade import (
|
||||||
DependencyException, OperationalException, exchange, persistence, __version__
|
DependencyException, OperationalException, exchange, persistence, __version__,
|
||||||
)
|
NotEnoughFundsExeption)
|
||||||
from freqtrade.analyze import Analyze
|
from freqtrade.analyze import Analyze
|
||||||
from freqtrade import constants
|
from freqtrade import constants
|
||||||
from freqtrade.fiat_convert import CryptoToFiatConverter
|
from freqtrade.fiat_convert import CryptoToFiatConverter
|
||||||
@ -24,7 +24,6 @@ from freqtrade.persistence import Trade
|
|||||||
from freqtrade.rpc.rpc_manager import RPCManager
|
from freqtrade.rpc.rpc_manager import RPCManager
|
||||||
from freqtrade.state import State
|
from freqtrade.state import State
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -166,7 +165,11 @@ class FreqtradeBot(object):
|
|||||||
|
|
||||||
# Then looking for buy opportunities
|
# Then looking for buy opportunities
|
||||||
if len(trades) < self.config['max_open_trades']:
|
if len(trades) < self.config['max_open_trades']:
|
||||||
state_changed = self.process_maybe_execute_buy()
|
try:
|
||||||
|
state_changed = self.process_maybe_execute_buy()
|
||||||
|
except NotEnoughFundsExeption:
|
||||||
|
logger.warning('insufficient funds to execute this buy order, ignoring it!')
|
||||||
|
state_changed = False
|
||||||
|
|
||||||
if 'unfilledtimeout' in self.config:
|
if 'unfilledtimeout' in self.config:
|
||||||
# Check and handle any timed out open orders
|
# Check and handle any timed out open orders
|
||||||
@ -176,13 +179,14 @@ class FreqtradeBot(object):
|
|||||||
except (requests.exceptions.RequestException, json.JSONDecodeError) as error:
|
except (requests.exceptions.RequestException, json.JSONDecodeError) as error:
|
||||||
logger.warning('%s, retrying in 30 seconds...', error)
|
logger.warning('%s, retrying in 30 seconds...', error)
|
||||||
time.sleep(constants.RETRY_TIMEOUT)
|
time.sleep(constants.RETRY_TIMEOUT)
|
||||||
|
|
||||||
except OperationalException:
|
except OperationalException:
|
||||||
self.rpc.send_msg(
|
self.rpc.send_msg(
|
||||||
'*Status:* OperationalException:\n```\n{traceback}```{hint}'
|
'*Status:* OperationalException:\n```\n{traceback}```{hint}'
|
||||||
.format(
|
.format(
|
||||||
traceback=traceback.format_exc(),
|
traceback=traceback.format_exc(),
|
||||||
hint='Issue `/start` if you think it is safe to restart.'
|
hint='Issue `/start` if you think it is safe to restart.'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
logger.exception('OperationalException. Stopping trader ...')
|
logger.exception('OperationalException. Stopping trader ...')
|
||||||
self.state = State.STOPPED
|
self.state = State.STOPPED
|
||||||
@ -301,16 +305,16 @@ class FreqtradeBot(object):
|
|||||||
# Create trade entity and return
|
# Create trade entity and return
|
||||||
self.rpc.send_msg(
|
self.rpc.send_msg(
|
||||||
'*{}:* Buying [{}]({}) with limit `{:.8f} ({:.6f} {}, {:.3f} {})` '
|
'*{}:* Buying [{}]({}) with limit `{:.8f} ({:.6f} {}, {:.3f} {})` '
|
||||||
.format(
|
.format(
|
||||||
exchange.get_name().upper(),
|
exchange.get_name().upper(),
|
||||||
pair.replace('_', '/'),
|
pair.replace('_', '/'),
|
||||||
exchange.get_pair_detail_url(pair),
|
exchange.get_pair_detail_url(pair),
|
||||||
buy_limit,
|
buy_limit,
|
||||||
stake_amount,
|
stake_amount,
|
||||||
self.config['stake_currency'],
|
self.config['stake_currency'],
|
||||||
stake_amount_fiat,
|
stake_amount_fiat,
|
||||||
self.config['fiat_display_currency']
|
self.config['fiat_display_currency']
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
|
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
@ -468,59 +472,64 @@ class FreqtradeBot(object):
|
|||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
# Execute sell and update trade record
|
# Execute sell and update trade record
|
||||||
order_id = exchange.sell(str(trade.pair), limit, trade.amount)
|
|
||||||
trade.open_order_id = order_id
|
|
||||||
|
|
||||||
fmt_exp_profit = round(trade.calc_profit_percent(rate=limit) * 100, 2)
|
# check if we have these funds first
|
||||||
profit_trade = trade.calc_profit(rate=limit)
|
try:
|
||||||
current_rate = exchange.get_ticker(trade.pair, False)['bid']
|
order_id = exchange.sell(str(trade.pair), limit, trade.amount)
|
||||||
profit = trade.calc_profit_percent(current_rate)
|
trade.open_order_id = order_id
|
||||||
|
|
||||||
message = "*{exchange}:* Selling\n" \
|
fmt_exp_profit = round(trade.calc_profit_percent(rate=limit) * 100, 2)
|
||||||
"*Current Pair:* [{pair}]({pair_url})\n" \
|
profit_trade = trade.calc_profit(rate=limit)
|
||||||
"*Limit:* `{limit}`\n" \
|
current_rate = exchange.get_ticker(trade.pair, False)['bid']
|
||||||
"*Amount:* `{amount}`\n" \
|
profit = trade.calc_profit_percent(current_rate)
|
||||||
"*Open Rate:* `{open_rate:.8f}`\n" \
|
|
||||||
"*Current Rate:* `{current_rate:.8f}`\n" \
|
|
||||||
"*Profit:* `{profit:.2f}%`" \
|
|
||||||
"".format(
|
|
||||||
exchange=trade.exchange,
|
|
||||||
pair=trade.pair,
|
|
||||||
pair_url=exchange.get_pair_detail_url(trade.pair),
|
|
||||||
limit=limit,
|
|
||||||
open_rate=trade.open_rate,
|
|
||||||
current_rate=current_rate,
|
|
||||||
amount=round(trade.amount, 8),
|
|
||||||
profit=round(profit * 100, 2),
|
|
||||||
)
|
|
||||||
|
|
||||||
# For regular case, when the configuration exists
|
message = "*{exchange}:* Selling\n" \
|
||||||
if 'stake_currency' in self.config and 'fiat_display_currency' in self.config:
|
"*Current Pair:* [{pair}]({pair_url})\n" \
|
||||||
fiat_converter = CryptoToFiatConverter()
|
"*Limit:* `{limit}`\n" \
|
||||||
profit_fiat = fiat_converter.convert_amount(
|
"*Amount:* `{amount}`\n" \
|
||||||
profit_trade,
|
"*Open Rate:* `{open_rate:.8f}`\n" \
|
||||||
self.config['stake_currency'],
|
"*Current Rate:* `{current_rate:.8f}`\n" \
|
||||||
self.config['fiat_display_currency']
|
"*Profit:* `{profit:.2f}%`" \
|
||||||
)
|
"".format(
|
||||||
message += '` ({gain}: {profit_percent:.2f}%, {profit_coin:.8f} {coin}`' \
|
exchange=trade.exchange,
|
||||||
'` / {profit_fiat:.3f} {fiat})`' \
|
pair=trade.pair,
|
||||||
''.format(
|
pair_url=exchange.get_pair_detail_url(trade.pair),
|
||||||
gain="profit" if fmt_exp_profit > 0 else "loss",
|
limit=limit,
|
||||||
profit_percent=fmt_exp_profit,
|
open_rate=trade.open_rate,
|
||||||
profit_coin=profit_trade,
|
current_rate=current_rate,
|
||||||
coin=self.config['stake_currency'],
|
amount=round(trade.amount, 8),
|
||||||
profit_fiat=profit_fiat,
|
profit=round(profit * 100, 2),
|
||||||
fiat=self.config['fiat_display_currency'],
|
)
|
||||||
)
|
|
||||||
# Because telegram._forcesell does not have the configuration
|
|
||||||
# Ignore the FIAT value and does not show the stake_currency as well
|
|
||||||
else:
|
|
||||||
message += '` ({gain}: {profit_percent:.2f}%, {profit_coin:.8f})`'.format(
|
|
||||||
gain="profit" if fmt_exp_profit > 0 else "loss",
|
|
||||||
profit_percent=fmt_exp_profit,
|
|
||||||
profit_coin=profit_trade
|
|
||||||
)
|
|
||||||
|
|
||||||
# Send the message
|
# For regular case, when the configuration exists
|
||||||
self.rpc.send_msg(message)
|
if 'stake_currency' in self.config and 'fiat_display_currency' in self.config:
|
||||||
Trade.session.flush()
|
fiat_converter = CryptoToFiatConverter()
|
||||||
|
profit_fiat = fiat_converter.convert_amount(
|
||||||
|
profit_trade,
|
||||||
|
self.config['stake_currency'],
|
||||||
|
self.config['fiat_display_currency']
|
||||||
|
)
|
||||||
|
message += '` ({gain}: {profit_percent:.2f}%, {profit_coin:.8f} {coin}`' \
|
||||||
|
'` / {profit_fiat:.3f} {fiat})`' \
|
||||||
|
''.format(
|
||||||
|
gain="profit" if fmt_exp_profit > 0 else "loss",
|
||||||
|
profit_percent=fmt_exp_profit,
|
||||||
|
profit_coin=profit_trade,
|
||||||
|
coin=self.config['stake_currency'],
|
||||||
|
profit_fiat=profit_fiat,
|
||||||
|
fiat=self.config['fiat_display_currency'],
|
||||||
|
)
|
||||||
|
# Because telegram._forcesell does not have the configuration
|
||||||
|
# Ignore the FIAT value and does not show the stake_currency as well
|
||||||
|
else:
|
||||||
|
message += '` ({gain}: {profit_percent:.2f}%, {profit_coin:.8f})`'.format(
|
||||||
|
gain="profit" if fmt_exp_profit > 0 else "loss",
|
||||||
|
profit_percent=fmt_exp_profit,
|
||||||
|
profit_coin=profit_trade
|
||||||
|
)
|
||||||
|
|
||||||
|
# Send the message
|
||||||
|
self.rpc.send_msg(message)
|
||||||
|
Trade.session.flush()
|
||||||
|
except NotEnoughFundsExeption:
|
||||||
|
logger.warning('Sell order failed, due to not having enough funds for %s.', trade)
|
||||||
|
1
freqtrade/tests/testdata/BTC_ADA-5.json
vendored
Normal file
1
freqtrade/tests/testdata/BTC_ADA-5.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_DASH-5.json
vendored
Normal file
1
freqtrade/tests/testdata/BTC_DASH-5.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_ETC-5.json
vendored
Normal file
1
freqtrade/tests/testdata/BTC_ETC-5.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_ETH-5.json
vendored
Normal file
1
freqtrade/tests/testdata/BTC_ETH-5.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_LTC-5.json
vendored
Normal file
1
freqtrade/tests/testdata/BTC_LTC-5.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_NXT-5.json
vendored
Normal file
1
freqtrade/tests/testdata/BTC_NXT-5.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_POWR-5.json
vendored
Normal file
1
freqtrade/tests/testdata/BTC_POWR-5.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_XLM-5.json
vendored
Normal file
1
freqtrade/tests/testdata/BTC_XLM-5.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_XMR-5.json
vendored
Normal file
1
freqtrade/tests/testdata/BTC_XMR-5.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_ZEC-5.json
vendored
Normal file
1
freqtrade/tests/testdata/BTC_ZEC-5.json
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user