Merge branch 'develop' of https://github.com/theluxaz/freqtrade into main
# Conflicts: # freqtrade/freqtradebot.py # freqtrade/optimize/backtesting.py
This commit is contained in:
@@ -83,10 +83,10 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
self.dataprovider = DataProvider(self.config, self.exchange, self.pairlists)
|
||||
|
||||
# Attach Dataprovider to Strategy baseclass
|
||||
IStrategy.dp = self.dataprovider
|
||||
# Attach Wallets to Strategy baseclass
|
||||
IStrategy.wallets = self.wallets
|
||||
# Attach Dataprovider to strategy instance
|
||||
self.strategy.dp = self.dataprovider
|
||||
# Attach Wallets to strategy instance
|
||||
self.strategy.wallets = self.wallets
|
||||
|
||||
# Initializing Edge only if enabled
|
||||
self.edge = Edge(self.config, self.exchange, self.strategy) if \
|
||||
@@ -99,7 +99,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
self.state = State[initial_state.upper()] if initial_state else State.STOPPED
|
||||
|
||||
# Protect sell-logic from forcesell and vice versa
|
||||
self._sell_lock = Lock()
|
||||
self._exit_lock = Lock()
|
||||
LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe))
|
||||
|
||||
def notify_status(self, msg: str) -> None:
|
||||
@@ -139,7 +139,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
# Only update open orders on startup
|
||||
# This will update the database after the initial migration
|
||||
self.update_open_orders()
|
||||
self.startup_update_open_orders()
|
||||
|
||||
def process(self) -> None:
|
||||
"""
|
||||
@@ -160,20 +160,20 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
# Refreshing candles
|
||||
self.dataprovider.refresh(self.pairlists.create_pair_list(self.active_pair_whitelist),
|
||||
self.strategy.informative_pairs())
|
||||
self.strategy.gather_informative_pairs())
|
||||
|
||||
strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)()
|
||||
|
||||
self.strategy.analyze(self.active_pair_whitelist)
|
||||
|
||||
with self._sell_lock:
|
||||
with self._exit_lock:
|
||||
# Check and handle any timed out open orders
|
||||
self.check_handle_timedout()
|
||||
|
||||
# Protect from collisions with forcesell.
|
||||
# Without this, freqtrade my try to recreate stoploss_on_exchange orders
|
||||
# while selling is in process, since telegram messages arrive in an different thread.
|
||||
with self._sell_lock:
|
||||
with self._exit_lock:
|
||||
trades = Trade.get_open_trades()
|
||||
# First process current opened trades (positions)
|
||||
self.exit_positions(trades)
|
||||
@@ -237,7 +237,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
open_trades = len(Trade.get_open_trades())
|
||||
return max(0, self.config['max_open_trades'] - open_trades)
|
||||
|
||||
def update_open_orders(self):
|
||||
def startup_update_open_orders(self):
|
||||
"""
|
||||
Updates open orders based on order list kept in the database.
|
||||
Mainly updates the state of orders - but may also close trades
|
||||
@@ -296,9 +296,9 @@ class FreqtradeBot(LoggingMixin):
|
||||
if sell_order:
|
||||
self.refind_lost_order(trade)
|
||||
else:
|
||||
self.reupdate_buy_order_fees(trade)
|
||||
self.reupdate_enter_order_fees(trade)
|
||||
|
||||
def reupdate_buy_order_fees(self, trade: Trade):
|
||||
def reupdate_enter_order_fees(self, trade: Trade):
|
||||
"""
|
||||
Get buy order from database, and try to reupdate.
|
||||
Handles trades where the initial fee-update did not work.
|
||||
@@ -476,21 +476,21 @@ class FreqtradeBot(LoggingMixin):
|
||||
time_in_force = self.strategy.order_time_in_force['buy']
|
||||
|
||||
if price:
|
||||
buy_limit_requested = price
|
||||
enter_limit_requested = price
|
||||
else:
|
||||
# Calculate price
|
||||
proposed_buy_rate = self.exchange.get_rate(pair, refresh=True, side="buy")
|
||||
proposed_enter_rate = self.exchange.get_rate(pair, refresh=True, side="buy")
|
||||
custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price,
|
||||
default_retval=proposed_buy_rate)(
|
||||
default_retval=proposed_enter_rate)(
|
||||
pair=pair, current_time=datetime.now(timezone.utc),
|
||||
proposed_rate=proposed_buy_rate)
|
||||
proposed_rate=proposed_enter_rate)
|
||||
|
||||
buy_limit_requested = self.get_valid_price(custom_entry_price, proposed_buy_rate)
|
||||
enter_limit_requested = self.get_valid_price(custom_entry_price, proposed_enter_rate)
|
||||
|
||||
if not buy_limit_requested:
|
||||
if not enter_limit_requested:
|
||||
raise PricingError('Could not determine buy price.')
|
||||
|
||||
min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, buy_limit_requested,
|
||||
min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, enter_limit_requested,
|
||||
self.strategy.stoploss)
|
||||
|
||||
if not self.edge:
|
||||
@@ -498,7 +498,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount,
|
||||
default_retval=stake_amount)(
|
||||
pair=pair, current_time=datetime.now(timezone.utc),
|
||||
current_rate=buy_limit_requested, proposed_stake=stake_amount,
|
||||
current_rate=enter_limit_requested, proposed_stake=stake_amount,
|
||||
min_stake=min_stake_amount, max_stake=max_stake_amount)
|
||||
stake_amount = self.wallets._validate_stake_amount(pair, stake_amount, min_stake_amount)
|
||||
|
||||
@@ -508,27 +508,27 @@ class FreqtradeBot(LoggingMixin):
|
||||
logger.info(f"Buy signal found: about create a new trade for {pair} with stake_amount: "
|
||||
f"{stake_amount} ...")
|
||||
|
||||
amount = stake_amount / buy_limit_requested
|
||||
amount = stake_amount / enter_limit_requested
|
||||
order_type = self.strategy.order_types['buy']
|
||||
if forcebuy:
|
||||
# Forcebuy can define a different ordertype
|
||||
order_type = self.strategy.order_types.get('forcebuy', order_type)
|
||||
|
||||
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
|
||||
pair=pair, order_type=order_type, amount=amount, rate=buy_limit_requested,
|
||||
pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
|
||||
time_in_force=time_in_force, current_time=datetime.now(timezone.utc)):
|
||||
logger.info(f"User requested abortion of buying {pair}")
|
||||
return False
|
||||
amount = self.exchange.amount_to_precision(pair, amount)
|
||||
order = self.exchange.create_order(pair=pair, ordertype=order_type, side="buy",
|
||||
amount=amount, rate=buy_limit_requested,
|
||||
amount=amount, rate=enter_limit_requested,
|
||||
time_in_force=time_in_force)
|
||||
order_obj = Order.parse_from_ccxt_object(order, pair, 'buy')
|
||||
order_id = order['id']
|
||||
order_status = order.get('status', None)
|
||||
|
||||
# we assume the order is executed at the price requested
|
||||
buy_limit_filled_price = buy_limit_requested
|
||||
enter_limit_filled_price = enter_limit_requested
|
||||
amount_requested = amount
|
||||
|
||||
if order_status == 'expired' or order_status == 'rejected':
|
||||
@@ -551,13 +551,13 @@ class FreqtradeBot(LoggingMixin):
|
||||
)
|
||||
stake_amount = order['cost']
|
||||
amount = safe_value_fallback(order, 'filled', 'amount')
|
||||
buy_limit_filled_price = safe_value_fallback(order, 'average', 'price')
|
||||
enter_limit_filled_price = safe_value_fallback(order, 'average', 'price')
|
||||
|
||||
# in case of FOK the order may be filled immediately and fully
|
||||
elif order_status == 'closed':
|
||||
stake_amount = order['cost']
|
||||
amount = safe_value_fallback(order, 'filled', 'amount')
|
||||
buy_limit_filled_price = safe_value_fallback(order, 'average', 'price')
|
||||
enter_limit_filled_price = safe_value_fallback(order, 'average', 'price')
|
||||
|
||||
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
|
||||
fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
|
||||
@@ -569,8 +569,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
amount_requested=amount_requested,
|
||||
fee_open=fee,
|
||||
fee_close=fee,
|
||||
open_rate=buy_limit_filled_price,
|
||||
open_rate_requested=buy_limit_requested,
|
||||
open_rate=enter_limit_filled_price,
|
||||
open_rate_requested=enter_limit_requested,
|
||||
open_date=datetime.utcnow(),
|
||||
exchange=self.exchange.id,
|
||||
open_order_id=order_id,
|
||||
@@ -590,11 +590,11 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Updating wallets
|
||||
self.wallets.update()
|
||||
|
||||
self._notify_buy(trade, order_type)
|
||||
self._notify_enter(trade, order_type)
|
||||
|
||||
return True
|
||||
|
||||
def _notify_buy(self, trade: Trade, order_type: str) -> None:
|
||||
def _notify_enter(self, trade: Trade, order_type: str) -> None:
|
||||
"""
|
||||
Sends rpc notification when a buy occurred.
|
||||
"""
|
||||
@@ -617,7 +617,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Send the message
|
||||
self.rpc.send_msg(msg)
|
||||
|
||||
def _notify_buy_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
|
||||
def _notify_enter_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
|
||||
"""
|
||||
Sends rpc notification when a buy cancel occurred.
|
||||
"""
|
||||
@@ -643,7 +643,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Send the message
|
||||
self.rpc.send_msg(msg)
|
||||
|
||||
def _notify_buy_fill(self, trade: Trade) -> None:
|
||||
def _notify_enter_fill(self, trade: Trade) -> None:
|
||||
msg = {
|
||||
'trade_id': trade.id,
|
||||
'type': RPCMessageType.BUY_FILL,
|
||||
@@ -746,7 +746,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
except InvalidOrderException as e:
|
||||
trade.stoploss_order_id = None
|
||||
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
||||
logger.warning('Selling the trade forcefully')
|
||||
logger.warning('Exiting the trade forcefully')
|
||||
self.execute_trade_exit(trade, trade.stop_loss, sell_reason=SellCheckTuple(
|
||||
sell_type=SellType.EMERGENCY_SELL))
|
||||
|
||||
@@ -784,7 +784,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Lock pair for one candle to prevent immediate rebuys
|
||||
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
|
||||
reason='Auto lock')
|
||||
self._notify_sell(trade, "stoploss")
|
||||
self._notify_exit(trade, "stoploss")
|
||||
return True
|
||||
|
||||
if trade.open_order_id or not trade.is_open:
|
||||
@@ -853,20 +853,20 @@ class FreqtradeBot(LoggingMixin):
|
||||
logger.warning(f"Could not create trailing stoploss order "
|
||||
f"for pair {trade.pair}.")
|
||||
|
||||
def _check_and_execute_sell(self, trade: Trade, sell_rate: float,
|
||||
def _check_and_execute_exit(self, trade: Trade, exit_rate: float,
|
||||
buy: bool, sell: bool, sell_tag: Optional[str]) -> bool:
|
||||
"""
|
||||
Check and execute sell
|
||||
Check and execute exit
|
||||
"""
|
||||
|
||||
should_sell = self.strategy.should_sell(
|
||||
trade, sell_rate, datetime.now(timezone.utc), buy, sell,
|
||||
trade, exit_rate, datetime.now(timezone.utc), buy, sell,
|
||||
force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0
|
||||
)
|
||||
|
||||
if should_sell.sell_flag:
|
||||
logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}. Tag: {sell_tag if sell_tag is not None else "None"}')
|
||||
self.execute_trade_exit(trade, sell_rate, should_sell,sell_tag)
|
||||
self.execute_trade_exit(trade, exit_rate, should_sell,sell_tag)
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -909,7 +909,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
default_retval=False)(pair=trade.pair,
|
||||
trade=trade,
|
||||
order=order))):
|
||||
self.handle_cancel_buy(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||
self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||
|
||||
elif (order['side'] == 'sell' and (order['status'] == 'open' or fully_cancelled) and (
|
||||
fully_cancelled
|
||||
@@ -918,7 +918,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
default_retval=False)(pair=trade.pair,
|
||||
trade=trade,
|
||||
order=order))):
|
||||
self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||
self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||
|
||||
def cancel_all_open_orders(self) -> None:
|
||||
"""
|
||||
@@ -934,13 +934,13 @@ class FreqtradeBot(LoggingMixin):
|
||||
continue
|
||||
|
||||
if order['side'] == 'buy':
|
||||
self.handle_cancel_buy(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
|
||||
self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
|
||||
|
||||
elif order['side'] == 'sell':
|
||||
self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
|
||||
self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
|
||||
Trade.commit()
|
||||
|
||||
def handle_cancel_buy(self, trade: Trade, order: Dict, reason: str) -> bool:
|
||||
def handle_cancel_enter(self, trade: Trade, order: Dict, reason: str) -> bool:
|
||||
"""
|
||||
Buy cancel - cancel order
|
||||
:return: True if order was fully cancelled
|
||||
@@ -997,11 +997,11 @@ class FreqtradeBot(LoggingMixin):
|
||||
reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}"
|
||||
|
||||
self.wallets.update()
|
||||
self._notify_buy_cancel(trade, order_type=self.strategy.order_types['buy'],
|
||||
reason=reason)
|
||||
self._notify_enter_cancel(trade, order_type=self.strategy.order_types['buy'],
|
||||
reason=reason)
|
||||
return was_trade_fully_canceled
|
||||
|
||||
def handle_cancel_sell(self, trade: Trade, order: Dict, reason: str) -> str:
|
||||
def handle_cancel_exit(self, trade: Trade, order: Dict, reason: str) -> str:
|
||||
"""
|
||||
Sell cancel - cancel order and update trade
|
||||
:return: Reason for cancel
|
||||
@@ -1035,14 +1035,14 @@ class FreqtradeBot(LoggingMixin):
|
||||
reason = constants.CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
|
||||
|
||||
self.wallets.update()
|
||||
self._notify_sell_cancel(
|
||||
self._notify_exit_cancel(
|
||||
trade,
|
||||
order_type=self.strategy.order_types['sell'],
|
||||
reason=reason
|
||||
)
|
||||
return reason
|
||||
|
||||
def _safe_sell_amount(self, pair: str, amount: float) -> float:
|
||||
def _safe_exit_amount(self, pair: str, amount: float) -> float:
|
||||
"""
|
||||
Get sellable amount.
|
||||
Should be trade.amount - but will fall back to the available amount if necessary.
|
||||
@@ -1114,7 +1114,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
# but we allow this value to be changed)
|
||||
order_type = self.strategy.order_types.get("forcesell", order_type)
|
||||
|
||||
amount = self._safe_sell_amount(trade.pair, trade.amount)
|
||||
amount = self._safe_exit_amount(trade.pair, trade.amount)
|
||||
time_in_force = self.strategy.order_time_in_force['sell']
|
||||
|
||||
if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)(
|
||||
@@ -1155,11 +1155,11 @@ class FreqtradeBot(LoggingMixin):
|
||||
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
|
||||
reason='Auto lock')
|
||||
|
||||
self._notify_sell(trade, order_type)
|
||||
self._notify_exit(trade, order_type)
|
||||
|
||||
return True
|
||||
|
||||
def _notify_sell(self, trade: Trade, order_type: str, fill: bool = False) -> None:
|
||||
def _notify_exit(self, trade: Trade, order_type: str, fill: bool = False) -> None:
|
||||
"""
|
||||
Sends rpc notification when a sell occurred.
|
||||
"""
|
||||
@@ -1201,7 +1201,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Send the message
|
||||
self.rpc.send_msg(msg)
|
||||
|
||||
def _notify_sell_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
|
||||
def _notify_exit_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
|
||||
"""
|
||||
Sends rpc notification when a sell cancel occurred.
|
||||
"""
|
||||
@@ -1222,7 +1222,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
'exchange': trade.exchange.capitalize(),
|
||||
'pair': trade.pair,
|
||||
'gain': gain,
|
||||
'limit': profit_rate,
|
||||
'limit': profit_rate or 0,
|
||||
'order_type': order_type,
|
||||
'amount': trade.amount,
|
||||
'open_rate': trade.open_rate,
|
||||
@@ -1231,7 +1231,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
'profit_ratio': profit_ratio,
|
||||
'sell_reason': trade.sell_reason,
|
||||
'open_date': trade.open_date,
|
||||
'close_date': trade.close_date,
|
||||
'close_date': trade.close_date or datetime.now(timezone.utc),
|
||||
'stake_currency': self.config['stake_currency'],
|
||||
'fiat_currency': self.config.get('fiat_display_currency', None),
|
||||
'reason': reason,
|
||||
@@ -1296,16 +1296,28 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Updating wallets when order is closed
|
||||
if not trade.is_open:
|
||||
if not stoploss_order and not trade.open_order_id:
|
||||
self._notify_sell(trade, '', True)
|
||||
self.protections.stop_per_pair(trade.pair)
|
||||
self.protections.global_stop()
|
||||
self._notify_exit(trade, '', True)
|
||||
self.handle_protections(trade.pair)
|
||||
self.wallets.update()
|
||||
elif not trade.open_order_id:
|
||||
# Buy fill
|
||||
self._notify_buy_fill(trade)
|
||||
self._notify_enter_fill(trade)
|
||||
|
||||
return False
|
||||
|
||||
def handle_protections(self, pair: str) -> None:
|
||||
prot_trig = self.protections.stop_per_pair(pair)
|
||||
if prot_trig:
|
||||
msg = {'type': RPCMessageType.PROTECTION_TRIGGER, }
|
||||
msg.update(prot_trig.to_json())
|
||||
self.rpc.send_msg(msg)
|
||||
|
||||
prot_trig_glb = self.protections.global_stop()
|
||||
if prot_trig_glb:
|
||||
msg = {'type': RPCMessageType.PROTECTION_TRIGGER_GLOBAL, }
|
||||
msg.update(prot_trig_glb.to_json())
|
||||
self.rpc.send_msg(msg)
|
||||
|
||||
def apply_fee_conditional(self, trade: Trade, trade_base_currency: str,
|
||||
amount: float, fee_abs: float) -> float:
|
||||
"""
|
||||
|
Reference in New Issue
Block a user