Merge branch 'freqtrade:develop' into rootless

This commit is contained in:
Maik H 2022-02-19 18:14:09 +01:00 committed by GitHub
commit d0c589a78c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 69 additions and 37 deletions

View File

@ -106,15 +106,18 @@ class Ftx(Exchange):
if order[0].get('status') == 'closed':
# Trigger order was triggered ...
real_order_id = order[0].get('info', {}).get('orderId')
# OrderId may be None for stoploss-market orders
# But contains "average" in these cases.
if real_order_id:
order1 = self._api.fetch_order(real_order_id, pair)
self._log_exchange_response('fetch_stoploss_order1', order1)
# Fake type to stop - as this was really a stop order.
order1['id_stop'] = order1['id']
order1['id'] = order_id
order1['type'] = 'stop'
order1['status_stop'] = 'triggered'
return order1
order1 = self._api.fetch_order(real_order_id, pair)
self._log_exchange_response('fetch_stoploss_order1', order1)
# Fake type to stop - as this was really a stop order.
order1['id_stop'] = order1['id']
order1['id'] = order_id
order1['type'] = 'stop'
order1['status_stop'] = 'triggered'
return order1
return order[0]
else:
raise InvalidOrderException(f"Could not get stoploss order for id {order_id}")

View File

@ -1021,12 +1021,12 @@ class FreqtradeBot(LoggingMixin):
# Cancelled orders may have the status of 'canceled' or 'closed'
if order['status'] not in constants.NON_OPEN_EXCHANGE_STATES:
filled_val = order.get('filled', 0.0) or 0.0
filled_val: float = order.get('filled', 0.0) or 0.0
filled_stake = filled_val * trade.open_rate
minstake = self.exchange.get_min_pair_stake_amount(
trade.pair, trade.open_rate, self.strategy.stoploss)
if filled_val > 0 and filled_stake < minstake:
if filled_val > 0 and minstake and filled_stake < minstake:
logger.warning(
f"Order {trade.open_order_id} for {trade.pair} not cancelled, "
f"as the filled amount of {filled_val} would result in an unsellable trade.")

View File

@ -29,18 +29,23 @@ def decimals_per_coin(coin: str):
return DECIMALS_PER_COIN.get(coin, DECIMAL_PER_COIN_FALLBACK)
def round_coin_value(value: float, coin: str, show_coin_name=True) -> str:
def round_coin_value(
value: float, coin: str, show_coin_name=True, keep_trailing_zeros=False) -> str:
"""
Get price value for this coin
:param value: Value to be printed
:param coin: Which coin are we printing the price / value for
:param show_coin_name: Return string in format: "222.22 USDT" or "222.22"
:param keep_trailing_zeros: Keep trailing zeros "222.200" vs. "222.2"
:return: Formatted / rounded value (with or without coin name)
"""
val = f"{value:.{decimals_per_coin(coin)}f}"
if not keep_trailing_zeros:
val = val.rstrip('0').rstrip('.')
if show_coin_name:
return f"{value:.{decimals_per_coin(coin)}f} {coin}"
else:
return f"{value:.{decimals_per_coin(coin)}f}"
val = f"{val} {coin}"
return val
def shorten_date(_date: str) -> str:

View File

@ -373,7 +373,7 @@ class HyperoptTools():
trials[f"Max Drawdown{' (Acct)' if has_account_drawdown else ''}"] = trials.apply(
lambda x: "{} {}".format(
round_coin_value(x['max_drawdown_abs'], stake_currency),
round_coin_value(x['max_drawdown_abs'], stake_currency, keep_trailing_zeros=True),
(f"({x['max_drawdown_account']:,.2%})"
if has_account_drawdown
else f"({x['max_drawdown']:,.2%})"
@ -388,7 +388,7 @@ class HyperoptTools():
trials['Profit'] = trials.apply(
lambda x: '{} {}'.format(
round_coin_value(x['Total profit'], stake_currency),
round_coin_value(x['Total profit'], stake_currency, keep_trailing_zeros=True),
f"({x['Profit']:,.2%})".rjust(10, ' ')
).rjust(25+len(stake_currency))
if x['Total profit'] != 0.0 else '--'.rjust(25+len(stake_currency)),

View File

@ -799,11 +799,11 @@ class Trade(_DECL_BASE, LocalTrade):
fee_close = Column(Float, nullable=False, default=0.0)
fee_close_cost = Column(Float, nullable=True)
fee_close_currency = Column(String(25), nullable=True)
open_rate = Column(Float)
open_rate: float = Column(Float)
open_rate_requested = Column(Float)
# open_trade_value - calculated via _calc_open_trade_value
open_trade_value = Column(Float)
close_rate = Column(Float)
close_rate: Optional[float] = Column(Float)
close_rate_requested = Column(Float)
close_profit = Column(Float)
close_profit_abs = Column(Float)

View File

@ -790,12 +790,13 @@ class Telegram(RPCHandler):
output = ''
if self._config['dry_run']:
output += "*Warning:* Simulated balances in Dry Mode.\n"
output += ("Starting capital: "
f"`{result['starting_capital']}` {self._config['stake_currency']}"
)
output += (f" `{result['starting_capital_fiat']}` "
f"{self._config['fiat_display_currency']}.\n"
starting_cap = round_coin_value(
result['starting_capital'], self._config['stake_currency'])
output += f"Starting capital: `{starting_cap}`"
starting_cap_fiat = round_coin_value(
result['starting_capital_fiat'], self._config['fiat_display_currency']
) if result['starting_capital_fiat'] > 0 else ''
output += (f" `, {starting_cap_fiat}`.\n"
) if result['starting_capital_fiat'] > 0 else '.\n'
total_dust_balance = 0

View File

@ -125,7 +125,7 @@ def test_stoploss_adjust_ftx(mocker, default_conf):
assert not exchange.stoploss_adjust(1501, order)
def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order):
def test_fetch_stoploss_order_ftx(default_conf, mocker, limit_sell_order):
default_conf['dry_run'] = True
order = MagicMock()
order.myid = 123
@ -147,9 +147,15 @@ def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order):
with pytest.raises(InvalidOrderException, match=r"Could not get stoploss order for id X"):
exchange.fetch_stoploss_order('X', 'TKN/BTC')['status']
api_mock.fetch_orders = MagicMock(return_value=[{'id': 'X', 'status': 'closed'}])
# stoploss Limit order
api_mock.fetch_orders = MagicMock(return_value=[
{'id': 'X', 'status': 'closed',
'info': {
'orderId': 'mocked_limit_sell',
}}])
api_mock.fetch_order = MagicMock(return_value=limit_sell_order)
# No orderId field - no call to fetch_order
resp = exchange.fetch_stoploss_order('X', 'TKN/BTC')
assert resp
assert api_mock.fetch_order.call_count == 1
@ -158,6 +164,17 @@ def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order):
assert resp['type'] == 'stop'
assert resp['status_stop'] == 'triggered'
# Stoploss market order
# Contains no new Order, but "average" instead
order = {'id': 'X', 'status': 'closed', 'info': {'orderId': None}, 'average': 0.254}
api_mock.fetch_orders = MagicMock(return_value=[order])
api_mock.fetch_order.reset_mock()
resp = exchange.fetch_stoploss_order('X', 'TKN/BTC')
assert resp
# fetch_order not called (no regular order ID)
assert api_mock.fetch_order.call_count == 0
assert order == order
with pytest.raises(InvalidOrderException):
api_mock.fetch_orders = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx')

View File

@ -770,7 +770,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
assert 'No closed trade' in msg_mock.call_args_list[-1][0][0]
assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0]
mocker.patch('freqtrade.wallets.Wallets.get_starting_balance', return_value=0.01)
assert ('∙ `-0.00000500 BTC (-0.50%) (-0.0 \N{GREEK CAPITAL LETTER SIGMA}%)`'
assert ('∙ `-0.000005 BTC (-0.50%) (-0.0 \N{GREEK CAPITAL LETTER SIGMA}%)`'
in msg_mock.call_args_list[-1][0][0])
msg_mock.reset_mock()
@ -845,7 +845,7 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick
assert '*XRP:*' not in result
assert 'Balance:' in result
assert 'Est. BTC:' in result
assert 'BTC: 12.00000000' in result
assert 'BTC: 12' in result
assert "*3 Other Currencies (< 0.0001 BTC):*" in result
assert 'BTC: 0.00000309' in result
@ -874,7 +874,7 @@ def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None
result = msg_mock.call_args_list[0][0][0]
assert msg_mock.call_count == 1
assert "*Warning:* Simulated balances in Dry Mode." in result
assert "Starting capital: `1000` BTC" in result
assert "Starting capital: `1000 BTC`" in result
def test_balance_handle_too_large_response(default_conf, update, mocker) -> None:
@ -1734,7 +1734,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
'pair': 'ETH/BTC',
'limit': 1.099e-05,
'order_type': 'limit',
'stake_amount': 0.001,
'stake_amount': 0.01465333,
'stake_amount_fiat': 0.0,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
@ -1751,7 +1751,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
'*Amount:* `1333.33333333`\n' \
'*Open Rate:* `0.00001099`\n' \
'*Current Rate:* `0.00001099`\n' \
'*Total:* `(0.00100000 BTC, 12.345 USD)`'
'*Total:* `(0.01465333 BTC, 180.895 USD)`'
freqtradebot.config['telegram']['notification_settings'] = {'buy': 'off'}
caplog.clear()
@ -1825,7 +1825,7 @@ def test_send_msg_buy_fill_notification(default_conf, mocker) -> None:
'buy_tag': 'buy_signal_01',
'exchange': 'Binance',
'pair': 'ETH/BTC',
'stake_amount': 0.001,
'stake_amount': 0.01465333,
# 'stake_amount_fiat': 0.0,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
@ -1839,7 +1839,7 @@ def test_send_msg_buy_fill_notification(default_conf, mocker) -> None:
'*Buy Tag:* `buy_signal_01`\n' \
'*Amount:* `1333.33333333`\n' \
'*Open Rate:* `0.00001099`\n' \
'*Total:* `(0.00100000 BTC, 12.345 USD)`'
'*Total:* `(0.01465333 BTC, 180.895 USD)`'
def test_send_msg_sell_notification(default_conf, mocker) -> None:
@ -2031,7 +2031,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
'pair': 'ETH/BTC',
'limit': 1.099e-05,
'order_type': 'limit',
'stake_amount': 0.001,
'stake_amount': 0.01465333,
'stake_amount_fiat': 0.0,
'stake_currency': 'BTC',
'fiat_currency': None,
@ -2044,7 +2044,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00001099`\n'
'*Current Rate:* `0.00001099`\n'
'*Total:* `(0.00100000 BTC)`')
'*Total:* `(0.01465333 BTC)`')
def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:

View File

@ -2436,6 +2436,9 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_buy_order_
mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock)
assert not freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
assert log_has_re(r"Order .* for .* not cancelled.", caplog)
# min_pair_stake empty should not crash
mocker.patch('freqtrade.exchange.Exchange.get_min_pair_stake_amount', return_value=None)
assert not freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason)
@pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'],

View File

@ -21,16 +21,19 @@ def test_decimals_per_coin():
def test_round_coin_value():
assert round_coin_value(222.222222, 'USDT') == '222.222 USDT'
assert round_coin_value(222.2, 'USDT') == '222.200 USDT'
assert round_coin_value(222.2, 'USDT', keep_trailing_zeros=True) == '222.200 USDT'
assert round_coin_value(222.2, 'USDT') == '222.2 USDT'
assert round_coin_value(222.12745, 'EUR') == '222.127 EUR'
assert round_coin_value(0.1274512123, 'BTC') == '0.12745121 BTC'
assert round_coin_value(0.1274512123, 'ETH') == '0.12745 ETH'
assert round_coin_value(222.222222, 'USDT', False) == '222.222'
assert round_coin_value(222.2, 'USDT', False) == '222.200'
assert round_coin_value(222.2, 'USDT', False) == '222.2'
assert round_coin_value(222.00, 'USDT', False) == '222'
assert round_coin_value(222.12745, 'EUR', False) == '222.127'
assert round_coin_value(0.1274512123, 'BTC', False) == '0.12745121'
assert round_coin_value(0.1274512123, 'ETH', False) == '0.12745'
assert round_coin_value(222.2, 'USDT', False, True) == '222.200'
def test_shorten_date() -> None: