Merge pull request #9750 from freqtrade/feat/order_tags

Add tags per order
This commit is contained in:
Matthias
2024-02-01 07:11:06 +01:00
committed by GitHub
12 changed files with 99 additions and 34 deletions
+6 -3
View File
@@ -767,6 +767,7 @@ This callback is **not** called when there is an open order (either buy or sell)
`adjust_trade_position()` is called very frequently for the duration of a trade, so you must keep your implementation as performant as possible. `adjust_trade_position()` is called very frequently for the duration of a trade, so you must keep your implementation as performant as possible.
Position adjustments will always be applied in the direction of the trade, so a positive value will always increase your position (negative values will decrease your position), no matter if it's a long or short trade. Position adjustments will always be applied in the direction of the trade, so a positive value will always increase your position (negative values will decrease your position), no matter if it's a long or short trade.
Adjustment orders can be assigned with a tag by returning a 2 element Tuple, with the first element being the adjustment amount, and the 2nd element the tag (e.g. `return 250, 'increase_favorable_conditions'`).
Modifications to leverage are not possible, and the stake-amount returned is assumed to be before applying leverage. Modifications to leverage are not possible, and the stake-amount returned is assumed to be before applying leverage.
@@ -833,7 +834,8 @@ class DigDeeperStrategy(IStrategy):
min_stake: Optional[float], max_stake: float, min_stake: Optional[float], max_stake: float,
current_entry_rate: float, current_exit_rate: float, current_entry_rate: float, current_exit_rate: float,
current_entry_profit: float, current_exit_profit: float, current_entry_profit: float, current_exit_profit: float,
**kwargs) -> Optional[float]: **kwargs
) -> Union[Optional[float], Tuple[Optional[float], Optional[str]]]:
""" """
Custom trade adjustment logic, returning the stake amount that a trade should be Custom trade adjustment logic, returning the stake amount that a trade should be
increased or decreased. increased or decreased.
@@ -859,11 +861,12 @@ class DigDeeperStrategy(IStrategy):
:return float: Stake amount to adjust your trade, :return float: Stake amount to adjust your trade,
Positive values to increase position, Negative values to decrease position. Positive values to increase position, Negative values to decrease position.
Return None for no action. Return None for no action.
Optionally, return a tuple with a 2nd element with an order reason
""" """
if current_profit > 0.05 and trade.nr_of_successful_exits == 0: if current_profit > 0.05 and trade.nr_of_successful_exits == 0:
# Take half of the profit at +5% # Take half of the profit at +5%
return -(trade.stake_amount / 2) return -(trade.stake_amount / 2), 'half_profit_5%'
if current_profit > -0.05: if current_profit > -0.05:
return None return None
@@ -891,7 +894,7 @@ class DigDeeperStrategy(IStrategy):
stake_amount = filled_entries[0].stake_amount stake_amount = filled_entries[0].stake_amount
# This then calculates current safety order size # This then calculates current safety order size
stake_amount = stake_amount * (1 + (count_of_entries * 0.25)) stake_amount = stake_amount * (1 + (count_of_entries * 0.25))
return stake_amount return stake_amount, '1/3rd_increase'
except Exception as exception: except Exception as exception:
return None return None
+6 -4
View File
@@ -645,8 +645,7 @@ class FreqtradeBot(LoggingMixin):
max_entry_stake = self.exchange.get_max_pair_stake_amount(trade.pair, current_entry_rate) max_entry_stake = self.exchange.get_max_pair_stake_amount(trade.pair, current_entry_rate)
stake_available = self.wallets.get_available_stake_amount() stake_available = self.wallets.get_available_stake_amount()
logger.debug(f"Calling adjust_trade_position for pair {trade.pair}") logger.debug(f"Calling adjust_trade_position for pair {trade.pair}")
stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, stake_amount, order_tag = self.strategy._adjust_trade_position_internal(
default_retval=None, supress_error=True)(
trade=trade, trade=trade,
current_time=datetime.now(timezone.utc), current_rate=current_entry_rate, current_time=datetime.now(timezone.utc), current_rate=current_entry_rate,
current_profit=current_entry_profit, min_stake=min_entry_stake, current_profit=current_entry_profit, min_stake=min_entry_stake,
@@ -665,7 +664,8 @@ class FreqtradeBot(LoggingMixin):
else: else:
logger.debug("Max adjustment entries is set to unlimited.") logger.debug("Max adjustment entries is set to unlimited.")
self.execute_entry(trade.pair, stake_amount, price=current_entry_rate, self.execute_entry(trade.pair, stake_amount, price=current_entry_rate,
trade=trade, is_short=trade.is_short, mode='pos_adjust') trade=trade, is_short=trade.is_short, mode='pos_adjust',
enter_tag=order_tag)
if stake_amount is not None and stake_amount < 0.0: if stake_amount is not None and stake_amount < 0.0:
# We should decrease our position # We should decrease our position
@@ -684,7 +684,7 @@ class FreqtradeBot(LoggingMixin):
return return
self.execute_trade_exit(trade, current_exit_rate, exit_check=ExitCheckTuple( self.execute_trade_exit(trade, current_exit_rate, exit_check=ExitCheckTuple(
exit_type=ExitType.PARTIAL_EXIT), sub_trade_amt=amount) exit_type=ExitType.PARTIAL_EXIT), sub_trade_amt=amount, exit_tag=order_tag)
def _check_depth_of_market(self, pair: str, conf: Dict, side: SignalDirection) -> bool: def _check_depth_of_market(self, pair: str, conf: Dict, side: SignalDirection) -> bool:
""" """
@@ -782,6 +782,7 @@ class FreqtradeBot(LoggingMixin):
leverage=leverage leverage=leverage
) )
order_obj = Order.parse_from_ccxt_object(order, pair, side, amount, enter_limit_requested) order_obj = Order.parse_from_ccxt_object(order, pair, side, amount, enter_limit_requested)
order_obj.ft_order_tag = enter_tag
order_id = order['id'] order_id = order['id']
order_status = order.get('status') order_status = order.get('status')
logger.info(f"Order {order_id} was created for {pair} and status is {order_status}.") logger.info(f"Order {order_id} was created for {pair} and status is {order_status}.")
@@ -1753,6 +1754,7 @@ class FreqtradeBot(LoggingMixin):
return False return False
order_obj = Order.parse_from_ccxt_object(order, trade.pair, trade.exit_side, amount, limit) order_obj = Order.parse_from_ccxt_object(order, trade.pair, trade.exit_side, amount, limit)
order_obj.ft_order_tag = exit_reason
trade.orders.append(order_obj) trade.orders.append(order_obj)
trade.exit_order_status = '' trade.exit_order_status = ''
+17 -11
View File
@@ -537,14 +537,14 @@ class Backtesting:
min_stake = self.exchange.get_min_pair_stake_amount(trade.pair, current_rate, -0.1) min_stake = self.exchange.get_min_pair_stake_amount(trade.pair, current_rate, -0.1)
max_stake = self.exchange.get_max_pair_stake_amount(trade.pair, current_rate) max_stake = self.exchange.get_max_pair_stake_amount(trade.pair, current_rate)
stake_available = self.wallets.get_available_stake_amount() stake_available = self.wallets.get_available_stake_amount()
stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, stake_amount, order_tag = self.strategy._adjust_trade_position_internal(
default_retval=None, supress_error=True)(
trade=trade, # type: ignore[arg-type] trade=trade, # type: ignore[arg-type]
current_time=current_time, current_rate=current_rate, current_time=current_time, current_rate=current_rate,
current_profit=current_profit, min_stake=min_stake, current_profit=current_profit, min_stake=min_stake,
max_stake=min(max_stake, stake_available), max_stake=min(max_stake, stake_available),
current_entry_rate=current_rate, current_exit_rate=current_rate, current_entry_rate=current_rate, current_exit_rate=current_rate,
current_entry_profit=current_profit, current_exit_profit=current_profit) current_entry_profit=current_profit, current_exit_profit=current_profit
)
# Check if we should increase our position # Check if we should increase our position
if stake_amount is not None and stake_amount > 0.0: if stake_amount is not None and stake_amount > 0.0:
@@ -554,7 +554,8 @@ class Backtesting:
check_adjust_entry = (entry_count <= self.strategy.max_entry_position_adjustment) check_adjust_entry = (entry_count <= self.strategy.max_entry_position_adjustment)
if check_adjust_entry: if check_adjust_entry:
pos_trade = self._enter_trade( pos_trade = self._enter_trade(
trade.pair, row, 'short' if trade.is_short else 'long', stake_amount, trade) trade.pair, row, 'short' if trade.is_short else 'long', stake_amount, trade,
entry_tag1=order_tag)
if pos_trade is not None: if pos_trade is not None:
self.wallets.update() self.wallets.update()
return pos_trade return pos_trade
@@ -569,7 +570,7 @@ class Backtesting:
if min_stake and remaining != 0 and remaining < min_stake: if min_stake and remaining != 0 and remaining < min_stake:
# Remaining stake is too low to be sold. # Remaining stake is too low to be sold.
return trade return trade
exit_ = ExitCheckTuple(ExitType.PARTIAL_EXIT) exit_ = ExitCheckTuple(ExitType.PARTIAL_EXIT, order_tag)
pos_trade = self._get_exit_for_signal(trade, row, exit_, current_time, amount) pos_trade = self._get_exit_for_signal(trade, row, exit_, current_time, amount)
if pos_trade is not None: if pos_trade is not None:
order = pos_trade.orders[-1] order = pos_trade.orders[-1]
@@ -681,11 +682,11 @@ class Backtesting:
trade.exit_reason = exit_reason trade.exit_reason = exit_reason
return self._exit_trade(trade, row, close_rate, amount_) return self._exit_trade(trade, row, close_rate, amount_, exit_reason)
return None return None
def _exit_trade(self, trade: LocalTrade, sell_row: Tuple, def _exit_trade(self, trade: LocalTrade, sell_row: Tuple, close_rate: float,
close_rate: float, amount: Optional[float] = None) -> Optional[LocalTrade]: amount: float, exit_reason: Optional[str]) -> Optional[LocalTrade]:
self.order_id_counter += 1 self.order_id_counter += 1
exit_candle_time = sell_row[DATE_IDX].to_pydatetime() exit_candle_time = sell_row[DATE_IDX].to_pydatetime()
order_type = self.strategy.order_types['exit'] order_type = self.strategy.order_types['exit']
@@ -712,6 +713,7 @@ class Backtesting:
filled=0, filled=0,
remaining=amount, remaining=amount,
cost=amount * close_rate, cost=amount * close_rate,
ft_order_tag=exit_reason,
) )
order._trade_bt = trade order._trade_bt = trade
trade.orders.append(order) trade.orders.append(order)
@@ -835,7 +837,9 @@ class Backtesting:
stake_amount: Optional[float] = None, stake_amount: Optional[float] = None,
trade: Optional[LocalTrade] = None, trade: Optional[LocalTrade] = None,
requested_rate: Optional[float] = None, requested_rate: Optional[float] = None,
requested_stake: Optional[float] = None) -> Optional[LocalTrade]: requested_stake: Optional[float] = None,
entry_tag1: Optional[str] = None
) -> Optional[LocalTrade]:
""" """
:param trade: Trade to adjust - initial entry if None :param trade: Trade to adjust - initial entry if None
:param requested_rate: Adjusted entry rate :param requested_rate: Adjusted entry rate
@@ -843,7 +847,7 @@ class Backtesting:
""" """
current_time = row[DATE_IDX].to_pydatetime() current_time = row[DATE_IDX].to_pydatetime()
entry_tag = row[ENTER_TAG_IDX] if len(row) >= ENTER_TAG_IDX + 1 else None entry_tag = entry_tag1 or (row[ENTER_TAG_IDX] if len(row) >= ENTER_TAG_IDX + 1 else None)
# let's call the custom entry price, using the open price as default price # let's call the custom entry price, using the open price as default price
order_type = self.strategy.order_types['entry'] order_type = self.strategy.order_types['entry']
pos_adjust = trade is not None and requested_rate is None pos_adjust = trade is not None and requested_rate is None
@@ -944,6 +948,7 @@ class Backtesting:
filled=0, filled=0,
remaining=amount, remaining=amount,
cost=amount * propose_rate + trade.fee_open, cost=amount * propose_rate + trade.fee_open,
ft_order_tag=entry_tag,
) )
order._trade_bt = trade order._trade_bt = trade
trade.orders.append(order) trade.orders.append(order)
@@ -963,7 +968,8 @@ class Backtesting:
# Ignore trade if entry-order did not fill yet # Ignore trade if entry-order did not fill yet
continue continue
exit_row = data[pair][-1] exit_row = data[pair][-1]
self._exit_trade(trade, exit_row, exit_row[OPEN_IDX], trade.amount) self._exit_trade(trade, exit_row, exit_row[OPEN_IDX], trade.amount,
ExitType.FORCE_EXIT.value)
trade.orders[-1].close_bt_order(exit_row[DATE_IDX].to_pydatetime(), trade) trade.orders[-1].close_bt_order(exit_row[DATE_IDX].to_pydatetime(), trade)
trade.close_date = exit_row[DATE_IDX].to_pydatetime() trade.close_date = exit_row[DATE_IDX].to_pydatetime()
+6 -4
View File
@@ -223,6 +223,7 @@ def migrate_orders_table(engine, table_back_name: str, cols_order: List):
ft_amount = get_column_def(cols_order, 'ft_amount', 'coalesce(amount, 0.0)') ft_amount = get_column_def(cols_order, 'ft_amount', 'coalesce(amount, 0.0)')
ft_price = get_column_def(cols_order, 'ft_price', 'coalesce(price, 0.0)') ft_price = get_column_def(cols_order, 'ft_price', 'coalesce(price, 0.0)')
ft_cancel_reason = get_column_def(cols_order, 'ft_cancel_reason', 'null') ft_cancel_reason = get_column_def(cols_order, 'ft_cancel_reason', 'null')
ft_order_tag = get_column_def(cols_order, 'ft_order_tag', 'null')
# sqlite does not support literals for booleans # sqlite does not support literals for booleans
with engine.begin() as connection: with engine.begin() as connection:
@@ -230,13 +231,14 @@ def migrate_orders_table(engine, table_back_name: str, cols_order: List):
insert into orders (id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, insert into orders (id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id,
status, symbol, order_type, side, price, amount, filled, average, remaining, cost, status, symbol, order_type, side, price, amount, filled, average, remaining, cost,
stop_price, order_date, order_filled_date, order_update_date, ft_fee_base, funding_fee, stop_price, order_date, order_filled_date, order_update_date, ft_fee_base, funding_fee,
ft_amount, ft_price, ft_cancel_reason ft_amount, ft_price, ft_cancel_reason, ft_order_tag
) )
select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id,
status, symbol, order_type, side, price, amount, filled, {average} average, remaining, status, symbol, order_type, side, price, amount, filled, {average} average, remaining,
cost, {stop_price} stop_price, order_date, order_filled_date, cost, {stop_price} stop_price, order_date, order_filled_date,
order_update_date, {ft_fee_base} ft_fee_base, {funding_fee} funding_fee, order_update_date, {ft_fee_base} ft_fee_base, {funding_fee} funding_fee,
{ft_amount} ft_amount, {ft_price} ft_price, {ft_cancel_reason} ft_cancel_reason {ft_amount} ft_amount, {ft_price} ft_price, {ft_cancel_reason} ft_cancel_reason,
{ft_order_tag} ft_order_tag
from {table_back_name} from {table_back_name}
""")) """))
@@ -331,8 +333,8 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
# if ('orders' not in previous_tables # if ('orders' not in previous_tables
# or not has_column(cols_orders, 'funding_fee')): # or not has_column(cols_orders, 'funding_fee')):
migrating = False migrating = False
# if not has_column(cols_orders, 'ft_cancel_reason'): # if not has_column(cols_trades, 'funding_fee_running'):
if not has_column(cols_trades, 'funding_fee_running'): if not has_column(cols_orders, 'ft_order_tag'):
migrating = True migrating = True
logger.info(f"Running database migration for trades - " logger.info(f"Running database migration for trades - "
f"backup: {table_back_name}, {order_table_bak_name}") f"backup: {table_back_name}, {order_table_bak_name}")
+8
View File
@@ -89,6 +89,8 @@ class Order(ModelBase):
funding_fee: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) funding_fee: Mapped[Optional[float]] = mapped_column(Float(), nullable=True)
ft_fee_base: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) ft_fee_base: Mapped[Optional[float]] = mapped_column(Float(), nullable=True)
ft_order_tag: Mapped[Optional[str]] = mapped_column(String(CUSTOM_TAG_MAX_LENGTH),
nullable=True)
@property @property
def order_date_utc(self) -> datetime: def order_date_utc(self) -> datetime:
@@ -212,6 +214,10 @@ class Order(ModelBase):
return order return order
def to_json(self, entry_side: str, minified: bool = False) -> Dict[str, Any]: def to_json(self, entry_side: str, minified: bool = False) -> Dict[str, Any]:
"""
:param minified: If True, only return a subset of the data is returned.
Only used for backtesting.
"""
resp = { resp = {
'amount': self.safe_amount, 'amount': self.safe_amount,
'safe_price': self.safe_price, 'safe_price': self.safe_price,
@@ -219,6 +225,7 @@ class Order(ModelBase):
'order_filled_timestamp': int(self.order_filled_date.replace( 'order_filled_timestamp': int(self.order_filled_date.replace(
tzinfo=timezone.utc).timestamp() * 1000) if self.order_filled_date else None, tzinfo=timezone.utc).timestamp() * 1000) if self.order_filled_date else None,
'ft_is_entry': self.ft_order_side == entry_side, 'ft_is_entry': self.ft_order_side == entry_side,
'ft_order_tag': self.ft_order_tag,
} }
if not minified: if not minified:
resp.update({ resp.update({
@@ -1405,6 +1412,7 @@ class LocalTrade:
ft_price=order["price"], ft_price=order["price"],
remaining=order["remaining"], remaining=order["remaining"],
funding_fee=order.get("funding_fee", None), funding_fee=order.get("funding_fee", None),
ft_order_tag=order.get("ft_order_tag", None),
) )
trade.orders.append(order_obj) trade.orders.append(order_obj)
+1
View File
@@ -261,6 +261,7 @@ class OrderSchema(BaseModel):
order_timestamp: Optional[int] = None order_timestamp: Optional[int] = None
order_filled_timestamp: Optional[int] = None order_filled_timestamp: Optional[int] = None
ft_fee_base: Optional[float] = None ft_fee_base: Optional[float] = None
ft_order_tag: Optional[str] = None
class TradeSchema(BaseModel): class TradeSchema(BaseModel):
+33 -1
View File
@@ -511,7 +511,8 @@ class IStrategy(ABC, HyperStrategyMixin):
min_stake: Optional[float], max_stake: float, min_stake: Optional[float], max_stake: float,
current_entry_rate: float, current_exit_rate: float, current_entry_rate: float, current_exit_rate: float,
current_entry_profit: float, current_exit_profit: float, current_entry_profit: float, current_exit_profit: float,
**kwargs) -> Optional[float]: **kwargs
) -> Union[Optional[float], Tuple[Optional[float], Optional[str]]]:
""" """
Custom trade adjustment logic, returning the stake amount that a trade should be Custom trade adjustment logic, returning the stake amount that a trade should be
increased or decreased. increased or decreased.
@@ -537,6 +538,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:return float: Stake amount to adjust your trade, :return float: Stake amount to adjust your trade,
Positive values to increase position, Negative values to decrease position. Positive values to increase position, Negative values to decrease position.
Return None for no action. Return None for no action.
Optionally, return a tuple with a 2nd element with an order reason
""" """
return None return None
@@ -725,6 +727,36 @@ class IStrategy(ABC, HyperStrategyMixin):
_ft_stop_uses_after_fill = False _ft_stop_uses_after_fill = False
def _adjust_trade_position_internal(
self, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float,
min_stake: Optional[float], max_stake: float,
current_entry_rate: float, current_exit_rate: float,
current_entry_profit: float, current_exit_profit: float,
**kwargs
) -> Tuple[Optional[float], str]:
"""
wrapper around adjust_trade_position to handle the return value
"""
resp = strategy_safe_wrapper(self.adjust_trade_position,
default_retval=(None, ''), supress_error=True)(
trade=trade, current_time=current_time,
current_rate=current_rate, current_profit=current_profit,
min_stake=min_stake, max_stake=max_stake,
current_entry_rate=current_entry_rate, current_exit_rate=current_exit_rate,
current_entry_profit=current_entry_profit, current_exit_profit=current_exit_profit,
**kwargs
)
order_tag = ''
if isinstance(resp, tuple):
if len(resp) >= 1:
stake_amount = resp[0]
if len(resp) > 1:
order_tag = resp[1] or ''
else:
stake_amount = resp
return stake_amount, order_tag
def __informative_pairs_freqai(self) -> ListPairsWithTimeframes: def __informative_pairs_freqai(self) -> ListPairsWithTimeframes:
""" """
Create informative-pairs needed for FreqAI Create informative-pairs needed for FreqAI
+8 -4
View File
@@ -742,14 +742,18 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
'orders': [ 'orders': [
[ [
{'amount': 0.00957442, 'safe_price': 0.104445, 'ft_order_side': 'buy', {'amount': 0.00957442, 'safe_price': 0.104445, 'ft_order_side': 'buy',
'order_filled_timestamp': 1517251200000, 'ft_is_entry': True}, 'order_filled_timestamp': 1517251200000, 'ft_is_entry': True,
'ft_order_tag': ''},
{'amount': 0.00957442, 'safe_price': 0.10496853383458644, 'ft_order_side': 'sell', {'amount': 0.00957442, 'safe_price': 0.10496853383458644, 'ft_order_side': 'sell',
'order_filled_timestamp': 1517265300000, 'ft_is_entry': False} 'order_filled_timestamp': 1517265300000, 'ft_is_entry': False,
'ft_order_tag': 'roi'}
], [ ], [
{'amount': 0.0097064, 'safe_price': 0.10302485, 'ft_order_side': 'buy', {'amount': 0.0097064, 'safe_price': 0.10302485, 'ft_order_side': 'buy',
'order_filled_timestamp': 1517283000000, 'ft_is_entry': True}, 'order_filled_timestamp': 1517283000000, 'ft_is_entry': True,
'ft_order_tag': ''},
{'amount': 0.0097064, 'safe_price': 0.10354126528822055, 'ft_order_side': 'sell', {'amount': 0.0097064, 'safe_price': 0.10354126528822055, 'ft_order_side': 'sell',
'order_filled_timestamp': 1517285400000, 'ft_is_entry': False} 'order_filled_timestamp': 1517285400000, 'ft_is_entry': False,
'ft_order_tag': 'roi'}
] ]
] ]
}) })
@@ -148,7 +148,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
assert pytest.approx(trade.amount) == 47.61904762 * leverage assert pytest.approx(trade.amount) == 47.61904762 * leverage
assert len(trade.orders) == 1 assert len(trade.orders) == 1
# Increase position by 100 # Increase position by 100
backtesting.strategy.adjust_trade_position = MagicMock(return_value=100) backtesting.strategy.adjust_trade_position = MagicMock(return_value=(100, 'PartIncrease'))
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time) trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time)
@@ -156,6 +156,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
assert pytest.approx(trade.stake_amount) == 200.0 assert pytest.approx(trade.stake_amount) == 200.0
assert pytest.approx(trade.amount) == 95.23809524 * leverage assert pytest.approx(trade.amount) == 95.23809524 * leverage
assert len(trade.orders) == 2 assert len(trade.orders) == 2
assert trade.orders[-1].ft_order_tag == 'PartIncrease'
assert pytest.approx(trade.liquidation_price) == (0.1038916 if leverage == 1 else 1.2127791) assert pytest.approx(trade.liquidation_price) == (0.1038916 if leverage == 1 else 1.2127791)
# Reduce by more than amount - no change to trade. # Reduce by more than amount - no change to trade.
@@ -171,13 +172,14 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
assert pytest.approx(trade.liquidation_price) == (0.1038916 if leverage == 1 else 1.2127791) assert pytest.approx(trade.liquidation_price) == (0.1038916 if leverage == 1 else 1.2127791)
# Reduce position by 50 # Reduce position by 50
backtesting.strategy.adjust_trade_position = MagicMock(return_value=-100) backtesting.strategy.adjust_trade_position = MagicMock(return_value=(-100, 'partDecrease'))
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time) trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time)
assert trade assert trade
assert pytest.approx(trade.stake_amount) == 100.0 assert pytest.approx(trade.stake_amount) == 100.0
assert pytest.approx(trade.amount) == 47.61904762 * leverage assert pytest.approx(trade.amount) == 47.61904762 * leverage
assert len(trade.orders) == 3 assert len(trade.orders) == 3
assert trade.orders[-1].ft_order_tag == 'partDecrease'
assert trade.nr_of_successful_entries == 2 assert trade.nr_of_successful_entries == 2
assert trade.nr_of_successful_exits == 1 assert trade.nr_of_successful_exits == 1
assert pytest.approx(trade.liquidation_price) == (0.1038916 if leverage == 1 else 1.2127791) assert pytest.approx(trade.liquidation_price) == (0.1038916 if leverage == 1 else 1.2127791)
+1 -1
View File
@@ -99,7 +99,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05,
'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY, 'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY,
'remaining': ANY, 'status': ANY, 'ft_is_entry': True, 'ft_fee_base': None, 'remaining': ANY, 'status': ANY, 'ft_is_entry': True, 'ft_fee_base': None,
'funding_fee': ANY, 'funding_fee': ANY, 'ft_order_tag': None,
}], }],
} }
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
+6 -2
View File
@@ -6725,11 +6725,15 @@ def test_check_and_call_adjust_trade_position(mocker, default_conf_usdt, fee, ca
) )
create_mock_trades(fee) create_mock_trades(fee)
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=10) freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(10, 'aaaa'))
freqtrade.process_open_trade_positions() freqtrade.process_open_trade_positions()
assert log_has_re(r"Max adjustment entries for .* has been reached\.", caplog) assert log_has_re(r"Max adjustment entries for .* has been reached\.", caplog)
assert freqtrade.strategy.adjust_trade_position.call_count == 1
caplog.clear() caplog.clear()
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-10) freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(-10, 'partial_exit_c'))
freqtrade.process_open_trade_positions() freqtrade.process_open_trade_positions()
assert log_has_re(r"LIMIT_SELL has been fulfilled.*", caplog) assert log_has_re(r"LIMIT_SELL has been fulfilled.*", caplog)
assert freqtrade.strategy.adjust_trade_position.call_count == 1
trade = Trade.get_trades(trade_filter=[Trade.id == 5]).first()
assert trade.orders[-1].ft_order_tag == 'partial_exit_c'
+3 -2
View File
@@ -536,7 +536,7 @@ def test_dca_order_adjust_entry_replace_fails(
# Create DCA order for 2nd trade (so we have 2 open orders on 2 trades) # Create DCA order for 2nd trade (so we have 2 open orders on 2 trades)
# this 2nd order won't fill. # this 2nd order won't fill.
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=20) freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(20, 'PeNF'))
freqtrade.process() freqtrade.process()
@@ -627,12 +627,13 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
assert log_has_re( assert log_has_re(
r"Remaining amount of \d\.\d+.* would be smaller than the minimum of 10.", caplog) r"Remaining amount of \d\.\d+.* would be smaller than the minimum of 10.", caplog)
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-20) freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(-20, 'PES'))
freqtrade.process() freqtrade.process()
trade = Trade.get_trades().first() trade = Trade.get_trades().first()
assert len(trade.orders) == 2 assert len(trade.orders) == 2
assert trade.orders[-1].ft_order_side == 'sell' assert trade.orders[-1].ft_order_side == 'sell'
assert trade.orders[-1].ft_order_tag == 'PES'
assert pytest.approx(trade.stake_amount) == 40.198 assert pytest.approx(trade.stake_amount) == 40.198
assert pytest.approx(trade.amount) == 20.099 * leverage assert pytest.approx(trade.amount) == 20.099 * leverage
assert trade.open_rate == 2.0 assert trade.open_rate == 2.0