Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 30 additions & 26 deletions freqtrade/freqtradebot.py
Original file line number Diff line number Diff line change
Expand Up @@ -671,15 +671,15 @@ def create_trade(self, pair: str) -> bool:
"""
logger.debug(f"create_trade for pair {pair}")

analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(pair, self.strategy.timeframe)
nowtime = analyzed_df.iloc[-1]["date"] if len(analyzed_df) > 0 else None

# get_free_open_trades is checked before create_trade is called
# but it is still used here to prevent opening too many trades within one iteration
if not self.get_free_open_trades():
logger.debug(f"Can't open a new trade for {pair}: max number of trades is reached.")
return False

analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(pair, self.strategy.timeframe)
nowtime = analyzed_df.iloc[-1]["date"] if len(analyzed_df) > 0 else None

# running get_signal on historical data fetched
(signal, enter_tag) = self.strategy.get_entry_signal(
pair, self.strategy.timeframe, analyzed_df
Expand Down Expand Up @@ -757,9 +757,6 @@ def check_and_call_adjust_trade_position(self, trade: Trade):
min_entry_stake = self.exchange.get_min_pair_stake_amount(
trade.pair, current_entry_rate, 0.0, trade.leverage
)
min_exit_stake = self.exchange.get_min_pair_stake_amount(
trade.pair, current_exit_rate, self.strategy.stoploss, trade.leverage
)
max_entry_stake = self.exchange.get_max_pair_stake_amount(
trade.pair, current_entry_rate, trade.leverage
)
Expand Down Expand Up @@ -823,6 +820,9 @@ def check_and_call_adjust_trade_position(self, trade: Trade):
return

remaining = (trade.amount - amount) * current_exit_rate
min_exit_stake = self.exchange.get_min_pair_stake_amount(
trade.pair, current_exit_rate, self.strategy.stoploss, trade.leverage
)
if min_exit_stake and remaining != 0 and remaining < min_exit_stake:
logger.info(
f"Remaining amount of {remaining} would be smaller "
Expand Down Expand Up @@ -1675,7 +1675,7 @@ def replace_order_failed(self, trade: Trade, msg: str) -> None:
)
trade.delete()

def replace_order(self, order: CcxtOrder, order_obj: Order | None, trade: Trade) -> None:
def replace_order(self, order: CcxtOrder, order_obj: Order, trade: Trade) -> None:
"""
Check if current analyzed entry order should be replaced or simply cancelled.
To simply cancel the existing order(no replacement) adjust_order_price() should return None
Expand All @@ -1694,7 +1694,7 @@ def replace_order(self, order: CcxtOrder, order_obj: Order | None, trade: Trade)
self.strategy.timeframe, latest_candle_open_date
)
# Check if new candle
if order_obj and latest_candle_close_date > order_obj.order_date_utc:
if latest_candle_close_date > order_obj.order_date_utc:
is_entry = order_obj.side == trade.entry_side
# New candle
proposed_rate = self.exchange.get_rate(
Expand Down Expand Up @@ -1877,17 +1877,18 @@ def handle_cancel_enter(

if order["status"] not in constants.NON_OPEN_EXCHANGE_STATES:
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 minstake and filled_stake < minstake:
logger.warning(
f"Order {order_id} for {trade.pair} not cancelled, "
f"as the filled amount of {filled_val} would result in an unexitable trade."
if filled_val > 0:
filled_stake = filled_val * trade.open_rate
minstake = self.exchange.get_min_pair_stake_amount(
trade.pair, trade.open_rate, self.strategy.stoploss
)
return False
if minstake and filled_stake < minstake:
logger.warning(
f"Order {order_id} for {trade.pair} not cancelled, "
f"as the filled amount of {filled_val} would result in an unexitable trade."
)
return False
corder = self.exchange.cancel_order_with_result(order_id, trade.pair, trade.amount)
order_obj.ft_cancel_reason = reason
# if replacing, retry fetching the order 3 times if the status is not what we need
Expand Down Expand Up @@ -1959,14 +1960,17 @@ def handle_cancel_exit(
# Cancelled orders may have the status of 'canceled' or 'closed'
if order["status"] not in constants.NON_OPEN_EXCHANGE_STATES:
filled_amt: float = order.get("filled", 0.0) or 0.0
# Filled val is in quote currency (after leverage)
filled_rem_stake = trade.stake_amount - (filled_amt * trade.open_rate / trade.leverage)
minstake = self.exchange.get_min_pair_stake_amount(
trade.pair, trade.open_rate, self.strategy.stoploss
)

# Double-check remaining amount
if filled_amt > 0:
reason = constants.CANCEL_REASON["PARTIALLY_FILLED"]
# Filled val is in quote currency (after leverage)
filled_rem_stake = trade.stake_amount - (
filled_amt * trade.open_rate / trade.leverage
)
minstake = self.exchange.get_min_pair_stake_amount(
trade.pair, trade.open_rate, self.strategy.stoploss
)
if minstake and filled_rem_stake < minstake:
logger.warning(
f"Order {order_id} for {trade.pair} not cancelled, as "
Expand Down Expand Up @@ -2104,8 +2108,8 @@ def execute_trade_exit(
proposed_limit_rate = limit
custom_exit_price = limit

current_profit = trade.calc_profit_ratio(limit)
if order_type == "limit" and not skip_custom_exit_price:
current_profit = trade.calc_profit_ratio(limit)
custom_exit_price = strategy_safe_wrapper(
self.strategy.custom_exit_price, default_retval=proposed_limit_rate
)(
Expand Down Expand Up @@ -2505,16 +2509,15 @@ def get_real_amount(self, trade: Trade, order: CcxtOrder, order_obj: Order) -> f
Necessary for exchanges which charge fees in base currency (e.g. binance)
:return: Absolute fee to apply for this order or None
"""
# Init variables
order_amount = safe_value_fallback(order, "filled", "amount")
# Only run for closed orders
if (
trade.fee_updated(order.get("side", "")) or order["status"] == "open"
# or order_obj.ft_fee_base
):
return None

trade_base_currency = self.exchange.get_pair_base_currency(trade.pair)
order_amount = safe_value_fallback(order, "filled", "amount")

# use fee from order-dict if possible
if self.exchange.order_has_fee(order):
fee_cost, fee_currency, fee_rate = self.exchange.extract_cost_curr_rate(
Expand All @@ -2529,6 +2532,7 @@ def get_real_amount(self, trade: Trade, order: CcxtOrder, order_obj: Order) -> f
# These are most likely caused by a parsing bug in ccxt
# due to multiple trades (https://github.com/ccxt/ccxt/issues/8025)
trade.update_fee(fee_cost, fee_currency, fee_rate, order.get("side", ""))
trade_base_currency = self.exchange.get_pair_base_currency(trade.pair)
if trade_base_currency == fee_currency:
# Apply fee to amount
return self.apply_fee_conditional(
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,13 @@ dependencies = [
"requests",
"httpx>=0.24.1",
"urllib3",
"certifi>=2026.1.4",
"jsonschema",
"scipy",
"numpy>2.0,<3.0",
"pandas>=2.2.0,<4.0",
"bottleneck>=1.5.0",
"numexpr>=2.14.0",
"TA-Lib<0.7",
"ft-pandas-ta",
"technical",
Expand Down
Loading