Reorganize, rename, redescribe and add new functionality
This commit is contained in:
parent
e5d4f7766e
commit
76c545ba0d
@ -22,6 +22,7 @@ from freqtrade.enums import (ExitCheckTuple, ExitType, RPCMessageType, RunMode,
|
|||||||
from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
|
from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
|
||||||
InvalidOrderException, PricingError)
|
InvalidOrderException, PricingError)
|
||||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
|
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
|
||||||
|
from freqtrade.exchange.exchange import timeframe_to_next_date
|
||||||
from freqtrade.misc import safe_value_fallback, safe_value_fallback2
|
from freqtrade.misc import safe_value_fallback, safe_value_fallback2
|
||||||
from freqtrade.mixins import LoggingMixin
|
from freqtrade.mixins import LoggingMixin
|
||||||
from freqtrade.persistence import Order, PairLocks, Trade, cleanup_db, init_db
|
from freqtrade.persistence import Order, PairLocks, Trade, cleanup_db, init_db
|
||||||
@ -188,8 +189,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
self.strategy.analyze(self.active_pair_whitelist)
|
self.strategy.analyze(self.active_pair_whitelist)
|
||||||
|
|
||||||
with self._exit_lock:
|
with self._exit_lock:
|
||||||
# Check and handle any timed out open orders
|
# Check for exchange cancelations, timeouts and user requested replace
|
||||||
self.check_handle_timedout()
|
self.manage_open_orders()
|
||||||
|
|
||||||
# Protect from collisions with force_exit.
|
# Protect from collisions with force_exit.
|
||||||
# Without this, freqtrade my try to recreate stoploss_on_exchange orders
|
# Without this, freqtrade my try to recreate stoploss_on_exchange orders
|
||||||
@ -1123,13 +1124,13 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_handle_timedout(self) -> None:
|
def manage_open_orders(self) -> None:
|
||||||
"""
|
"""
|
||||||
Check if any orders are timed out and cancel if necessary
|
Management of open orders on exchange. Unfilled orders might be cancelled if timeout
|
||||||
:param timeoutvalue: Number of minutes until order is considered timed out
|
was met or replaced if there's a new candle and user has requested it.
|
||||||
|
Timeout setting takes priority over limit order adjustment request.
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for trade in Trade.get_open_order_trades():
|
for trade in Trade.get_open_order_trades():
|
||||||
try:
|
try:
|
||||||
if not trade.open_order_id:
|
if not trade.open_order_id:
|
||||||
@ -1140,34 +1141,79 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
fully_cancelled = self.update_trade_state(trade, trade.open_order_id, order)
|
fully_cancelled = self.update_trade_state(trade, trade.open_order_id, order)
|
||||||
is_entering = order['side'] == trade.entry_side
|
|
||||||
not_closed = order['status'] == 'open' or fully_cancelled
|
not_closed = order['status'] == 'open' or fully_cancelled
|
||||||
max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0)
|
|
||||||
|
|
||||||
order_obj = trade.select_order_by_order_id(trade.open_order_id)
|
order_obj = trade.select_order_by_order_id(trade.open_order_id)
|
||||||
|
|
||||||
if not_closed and (fully_cancelled or (order_obj and self.strategy.ft_check_timed_out(
|
if not_closed:
|
||||||
trade, order_obj, datetime.now(timezone.utc)))
|
if fully_cancelled or (order_obj and self.strategy.ft_check_timed_out(
|
||||||
):
|
trade, order_obj, datetime.now(timezone.utc))):
|
||||||
if is_entering:
|
self.handle_timedout_orders(order, trade)
|
||||||
|
else:
|
||||||
|
self.replace_orders(order, order_obj, trade)
|
||||||
|
|
||||||
|
def handle_timedout_orders(self, order: Dict, trade: Trade) -> None:
|
||||||
|
"""
|
||||||
|
Check if any orders are timed out and cancel if necessary.
|
||||||
|
:param order: Order dict grabbed with exchange.fetch_order()
|
||||||
|
:param trade: Trade object.
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
if order['side'] == trade.entry_side:
|
||||||
self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||||
else:
|
else:
|
||||||
canceled = self.handle_cancel_exit(
|
canceled = self.handle_cancel_exit(
|
||||||
trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||||
canceled_count = trade.get_exit_order_count()
|
canceled_count = trade.get_exit_order_count()
|
||||||
max_timeouts = self.config.get(
|
max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0)
|
||||||
'unfilledtimeout', {}).get('exit_timeout_count', 0)
|
|
||||||
if canceled and max_timeouts > 0 and canceled_count >= max_timeouts:
|
if canceled and max_timeouts > 0 and canceled_count >= max_timeouts:
|
||||||
logger.warning(f'Emergency exiting trade {trade}, as the exit order '
|
logger.warning(f'Emergency exiting trade {trade}, as the exit order '
|
||||||
f'timed out {max_timeouts} times.')
|
f'timed out {max_timeouts} times.')
|
||||||
try:
|
try:
|
||||||
self.execute_trade_exit(
|
self.execute_trade_exit(
|
||||||
trade, order.get('price'),
|
trade, order['price'],
|
||||||
exit_check=ExitCheckTuple(exit_type=ExitType.EMERGENCY_EXIT))
|
exit_check=ExitCheckTuple(exit_type=ExitType.EMERGENCY_EXIT))
|
||||||
except DependencyException as exception:
|
except DependencyException as exception:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f'Unable to emergency sell trade {trade.pair}: {exception}')
|
f'Unable to emergency sell trade {trade.pair}: {exception}')
|
||||||
|
|
||||||
|
def replace_orders(self, order: Dict, order_obj: Optional[Order], trade: Trade) -> None:
|
||||||
|
"""
|
||||||
|
Check if any orders should be replaced and do so
|
||||||
|
:param order: Order dict grabbed with exchange.fetch_order()
|
||||||
|
:param order_obj: Order object.
|
||||||
|
:param trade: Trade object.
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair,
|
||||||
|
self.strategy.timeframe)
|
||||||
|
latest_candle_open_date = analyzed_df.iloc[-1]['date'] if len(analyzed_df) > 0 else None
|
||||||
|
latest_candle_close_date = timeframe_to_next_date(self.strategy.timeframe,
|
||||||
|
latest_candle_open_date)
|
||||||
|
# Check if new candle
|
||||||
|
if order_obj and latest_candle_close_date.replace(tzinfo=None) > order_obj.order_date:
|
||||||
|
# New candle
|
||||||
|
proposed_rate = self.exchange.get_rate(
|
||||||
|
trade.pair, side='entry', is_short=trade.is_short, refresh=True)
|
||||||
|
adjusted_entry_price = strategy_safe_wrapper(self.strategy.readjust_entry_price,
|
||||||
|
default_retval=proposed_rate)(
|
||||||
|
pair=trade.pair, current_time=datetime.now(timezone.utc),
|
||||||
|
proposed_rate=proposed_rate, entry_tag=trade.enter_tag,
|
||||||
|
side=trade.entry_side)
|
||||||
|
# check if user has requested entry limit adjustment
|
||||||
|
if proposed_rate != adjusted_entry_price:
|
||||||
|
# cancel existing order
|
||||||
|
self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['REPLACE'],
|
||||||
|
allow_full_cancel=False)
|
||||||
|
stake = self.wallets.get_trade_stake_amount(trade.pair, self.edge)
|
||||||
|
# place new order with requested price
|
||||||
|
self.execute_entry(
|
||||||
|
pair=trade.pair,
|
||||||
|
stake_amount=stake,
|
||||||
|
price=adjusted_entry_price,
|
||||||
|
trade=trade,
|
||||||
|
is_short=trade.is_short
|
||||||
|
)
|
||||||
|
|
||||||
def cancel_all_open_orders(self) -> None:
|
def cancel_all_open_orders(self) -> None:
|
||||||
"""
|
"""
|
||||||
Cancel all orders that are currently open
|
Cancel all orders that are currently open
|
||||||
|
Loading…
Reference in New Issue
Block a user