Merge branch 'feat/short' into pr/samgermain/6467
This commit is contained in:
commit
1b07ad92cf
@ -593,6 +593,8 @@ Additional orders also result in additional fees and those orders don't count to
|
||||
This callback is **not** called when there is an open order (either buy or sell) waiting for execution, or when you have reached the maximum amount of extra buys that you have set on `max_entry_position_adjustment`.
|
||||
`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, no matter if it's a long or short trade. Modifications to leverage are not possible.
|
||||
|
||||
!!! Note "About stake size"
|
||||
Using fixed stake size means it will be the amount used for the first order, just like without position adjustment.
|
||||
If you wish to buy additional orders with DCA, then make sure to leave enough funds in the wallet for that.
|
||||
@ -663,7 +665,7 @@ class DigDeeperStrategy(IStrategy):
|
||||
return None
|
||||
|
||||
filled_buys = trade.select_filled_orders('buy')
|
||||
count_of_buys = trade.nr_of_successful_buys
|
||||
count_of_entries = trade.nr_of_successful_entries
|
||||
# Allow up to 3 additional increasingly larger buys (4 in total)
|
||||
# Initial buy is 1x
|
||||
# If that falls to -5% profit, we buy 1.25x more, average profit should increase to roughly -2.2%
|
||||
@ -676,7 +678,7 @@ class DigDeeperStrategy(IStrategy):
|
||||
# This returns first order stake size
|
||||
stake_amount = filled_buys[0].cost
|
||||
# This then calculates current safety order size
|
||||
stake_amount = stake_amount * (1 + (count_of_buys * 0.25))
|
||||
stake_amount = stake_amount * (1 + (count_of_entries * 0.25))
|
||||
return stake_amount
|
||||
except Exception as exception:
|
||||
return None
|
||||
|
@ -103,7 +103,6 @@ class FreqtradeBot(LoggingMixin):
|
||||
self._exit_lock = Lock()
|
||||
LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe))
|
||||
|
||||
self.liquidation_buffer = float(self.config.get('liquidation_buffer', '0.05'))
|
||||
self.trading_mode: TradingMode = self.config.get('trading_mode', TradingMode.SPOT)
|
||||
self.margin_mode_type: Optional[MarginMode] = None
|
||||
if 'margin_mode' in self.config:
|
||||
@ -510,7 +509,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
"""
|
||||
# TODO-lev: Check what changes are necessary for DCA in relation to shorts.
|
||||
if self.strategy.max_entry_position_adjustment > -1:
|
||||
count_of_buys = trade.nr_of_successful_buys
|
||||
count_of_buys = trade.nr_of_successful_entries
|
||||
if count_of_buys > self.strategy.max_entry_position_adjustment:
|
||||
logger.debug(f"Max adjustment entries for {trade.pair} has been reached.")
|
||||
return
|
||||
@ -533,7 +532,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
if stake_amount is not None and stake_amount > 0.0:
|
||||
# We should increase our position
|
||||
self.execute_entry(trade.pair, stake_amount, trade=trade)
|
||||
self.execute_entry(trade.pair, stake_amount, trade=trade, is_short=trade.is_short)
|
||||
|
||||
if stake_amount is not None and stake_amount < 0.0:
|
||||
# We should decrease our position
|
||||
@ -607,18 +606,21 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
if not stake_amount:
|
||||
return False
|
||||
|
||||
max_leverage = self.exchange.get_max_leverage(pair, stake_amount)
|
||||
leverage = strategy_safe_wrapper(self.strategy.leverage, default_retval=1.0)(
|
||||
pair=pair,
|
||||
current_time=datetime.now(timezone.utc),
|
||||
current_rate=enter_limit_requested,
|
||||
proposed_leverage=1.0,
|
||||
max_leverage=max_leverage,
|
||||
side=trade_side,
|
||||
) if self.trading_mode != TradingMode.SPOT else 1.0
|
||||
# Cap leverage between 1.0 and max_leverage.
|
||||
leverage = min(max(leverage, 1.0), max_leverage)
|
||||
if not pos_adjust:
|
||||
max_leverage = self.exchange.get_max_leverage(pair, stake_amount)
|
||||
leverage = strategy_safe_wrapper(self.strategy.leverage, default_retval=1.0)(
|
||||
pair=pair,
|
||||
current_time=datetime.now(timezone.utc),
|
||||
current_rate=enter_limit_requested,
|
||||
proposed_leverage=1.0,
|
||||
max_leverage=max_leverage,
|
||||
side=trade_side,
|
||||
) if self.trading_mode != TradingMode.SPOT else 1.0
|
||||
# Cap leverage between 1.0 and max_leverage.
|
||||
leverage = min(max(leverage, 1.0), max_leverage)
|
||||
else:
|
||||
# Changing leverage currently not possible
|
||||
leverage = trade.leverage if trade else 1.0
|
||||
if pos_adjust:
|
||||
logger.info(f"Position adjust: about to create a new order for {pair} with stake: "
|
||||
f"{stake_amount} for {trade}")
|
||||
@ -688,6 +690,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
amount = safe_value_fallback(order, 'filled', 'amount')
|
||||
enter_limit_filled_price = safe_value_fallback(order, 'average', 'price')
|
||||
|
||||
# TODO: this might be unnecessary, as we're calling it in update_trade_state.
|
||||
isolated_liq = self.exchange.get_liquidation_price(
|
||||
leverage=leverage,
|
||||
pair=pair,
|
||||
@ -1030,7 +1033,11 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
# If enter order is fulfilled but there is no stoploss, we add a stoploss on exchange
|
||||
if not stoploss_order:
|
||||
stoploss = self.edge.stoploss(pair=trade.pair) if self.edge else self.strategy.stoploss
|
||||
stoploss = (
|
||||
self.edge.stoploss(pair=trade.pair)
|
||||
if self.edge else
|
||||
self.strategy.stoploss / trade.leverage
|
||||
)
|
||||
if trade.is_short:
|
||||
stop_price = trade.open_rate * (1 - stoploss)
|
||||
else:
|
||||
@ -1557,9 +1564,20 @@ class FreqtradeBot(LoggingMixin):
|
||||
Trade.commit()
|
||||
|
||||
if order['status'] in constants.NON_OPEN_EXCHANGE_STATES:
|
||||
# If a buy order was closed, force update on stoploss on exchange
|
||||
if order.get('side', None) == 'buy':
|
||||
# If a entry order was closed, force update on stoploss on exchange
|
||||
if order.get('side', None) == trade.enter_side:
|
||||
trade = self.cancel_stoploss_on_exchange(trade)
|
||||
# TODO: Margin will need to use interest_rate as well.
|
||||
# interest_rate = self.exchange.get_interest_rate()
|
||||
trade.set_isolated_liq(self.exchange.get_liquidation_price(
|
||||
|
||||
leverage=trade.leverage,
|
||||
pair=trade.pair,
|
||||
amount=trade.amount,
|
||||
open_rate=trade.open_rate,
|
||||
is_short=trade.is_short
|
||||
))
|
||||
|
||||
# Updating wallets when order is closed
|
||||
self.wallets.update()
|
||||
|
||||
@ -1568,7 +1586,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
self._notify_exit(trade, '', True)
|
||||
self.handle_protections(trade.pair)
|
||||
elif send_msg and not trade.open_order_id:
|
||||
# Buy fill
|
||||
# Enter fill
|
||||
self._notify_enter(trade, order, fill=True)
|
||||
|
||||
return False
|
||||
|
@ -465,11 +465,11 @@ class Backtesting:
|
||||
|
||||
# Check if we need to adjust our current positions
|
||||
if self.strategy.position_adjustment_enable:
|
||||
check_adjust_buy = True
|
||||
check_adjust_entry = True
|
||||
if self.strategy.max_entry_position_adjustment > -1:
|
||||
count_of_buys = trade.nr_of_successful_buys
|
||||
check_adjust_buy = (count_of_buys <= self.strategy.max_entry_position_adjustment)
|
||||
if check_adjust_buy:
|
||||
entry_count = trade.nr_of_successful_entries
|
||||
check_adjust_entry = (entry_count <= self.strategy.max_entry_position_adjustment)
|
||||
if check_adjust_entry:
|
||||
trade = self._get_adjust_trade_entry_for_candle(trade, sell_row)
|
||||
|
||||
sell_candle_time: datetime = sell_row[DATE_IDX].to_pydatetime()
|
||||
@ -640,17 +640,20 @@ class Backtesting:
|
||||
# If not pos adjust, trade is None
|
||||
return trade
|
||||
|
||||
max_leverage = self.exchange.get_max_leverage(pair, stake_amount)
|
||||
leverage = strategy_safe_wrapper(self.strategy.leverage, default_retval=1.0)(
|
||||
pair=pair,
|
||||
current_time=current_time,
|
||||
current_rate=row[OPEN_IDX],
|
||||
proposed_leverage=1.0,
|
||||
max_leverage=max_leverage,
|
||||
side=direction,
|
||||
) if self._can_short else 1.0
|
||||
# Cap leverage between 1.0 and max_leverage.
|
||||
leverage = min(max(leverage, 1.0), max_leverage)
|
||||
if not pos_adjust:
|
||||
max_leverage = self.exchange.get_max_leverage(pair, stake_amount)
|
||||
leverage = strategy_safe_wrapper(self.strategy.leverage, default_retval=1.0)(
|
||||
pair=pair,
|
||||
current_time=current_time,
|
||||
current_rate=row[OPEN_IDX],
|
||||
proposed_leverage=1.0,
|
||||
max_leverage=max_leverage,
|
||||
side=direction,
|
||||
) if self._can_short else 1.0
|
||||
# Cap leverage between 1.0 and max_leverage.
|
||||
leverage = min(max(leverage, 1.0), max_leverage)
|
||||
else:
|
||||
leverage = trade.leverage if trade else 1.0
|
||||
|
||||
order_type = self.strategy.order_types['buy']
|
||||
time_in_force = self.strategy.order_time_in_force['buy']
|
||||
@ -745,7 +748,7 @@ class Backtesting:
|
||||
for pair in open_trades.keys():
|
||||
if len(open_trades[pair]) > 0:
|
||||
for trade in open_trades[pair]:
|
||||
if trade.open_order_id and trade.nr_of_successful_buys == 0:
|
||||
if trade.open_order_id and trade.nr_of_successful_entries == 0:
|
||||
# Ignore trade if buy-order did not fill yet
|
||||
continue
|
||||
sell_row = data[pair][-1]
|
||||
@ -798,7 +801,7 @@ class Backtesting:
|
||||
if timedout:
|
||||
if order.side == 'buy':
|
||||
self.timedout_entry_orders += 1
|
||||
if trade.nr_of_successful_buys == 0:
|
||||
if trade.nr_of_successful_entries == 0:
|
||||
# Remove trade due to buy timeout expiration.
|
||||
return True
|
||||
else:
|
||||
|
@ -568,13 +568,14 @@ class LocalTrade():
|
||||
# Don't modify if called with initial and nothing to do
|
||||
return
|
||||
|
||||
leverage = self.leverage or 1.0
|
||||
if self.is_short:
|
||||
new_loss = float(current_price * (1 + abs(stoploss)))
|
||||
new_loss = float(current_price * (1 + abs(stoploss / leverage)))
|
||||
# If trading with leverage, don't set the stoploss below the liquidation price
|
||||
if self.isolated_liq:
|
||||
new_loss = min(self.isolated_liq, new_loss)
|
||||
else:
|
||||
new_loss = float(current_price * (1 - abs(stoploss)))
|
||||
new_loss = float(current_price * (1 - abs(stoploss / leverage)))
|
||||
# If trading with leverage, don't set the stoploss below the liquidation price
|
||||
if self.isolated_liq:
|
||||
new_loss = max(self.isolated_liq, new_loss)
|
||||
@ -868,7 +869,7 @@ class LocalTrade():
|
||||
|
||||
def recalc_trade_from_orders(self):
|
||||
# We need at least 2 entry orders for averaging amounts and rates.
|
||||
if len(self.select_filled_orders('buy')) < 2:
|
||||
if len(self.select_filled_orders(self.enter_side)) < 2:
|
||||
# Just in case, still recalc open trade value
|
||||
self.recalc_open_trade_value()
|
||||
return
|
||||
@ -890,8 +891,9 @@ class LocalTrade():
|
||||
total_stake += tmp_price * tmp_amount
|
||||
|
||||
if total_amount > 0:
|
||||
# Leverage not updated, as we don't allow changing leverage through DCA at the moment.
|
||||
self.open_rate = total_stake / total_amount
|
||||
self.stake_amount = total_stake
|
||||
self.stake_amount = total_stake / (self.leverage or 1.0)
|
||||
self.amount = total_amount
|
||||
self.fee_open_cost = self.fee_open * self.stake_amount
|
||||
self.recalc_open_trade_value()
|
||||
@ -937,10 +939,28 @@ class LocalTrade():
|
||||
(o.filled or 0) > 0 and
|
||||
o.status in NON_OPEN_EXCHANGE_STATES]
|
||||
|
||||
@property
|
||||
def nr_of_successful_entries(self) -> int:
|
||||
"""
|
||||
Helper function to count the number of entry orders that have been filled.
|
||||
:return: int count of entry orders that have been filled for this trade.
|
||||
"""
|
||||
|
||||
return len(self.select_filled_orders(self.enter_side))
|
||||
|
||||
@property
|
||||
def nr_of_successful_exits(self) -> int:
|
||||
"""
|
||||
Helper function to count the number of exit orders that have been filled.
|
||||
:return: int count of exit orders that have been filled for this trade.
|
||||
"""
|
||||
return len(self.select_filled_orders(self.exit_side))
|
||||
|
||||
@property
|
||||
def nr_of_successful_buys(self) -> int:
|
||||
"""
|
||||
Helper function to count the number of buy orders that have been filled.
|
||||
WARNING: Please use nr_of_successful_entries for short support.
|
||||
:return: int count of buy orders that have been filled for this trade.
|
||||
"""
|
||||
|
||||
@ -950,6 +970,7 @@ class LocalTrade():
|
||||
def nr_of_successful_sells(self) -> int:
|
||||
"""
|
||||
Helper function to count the number of sell orders that have been filled.
|
||||
WARNING: Please use nr_of_successful_exits for short support.
|
||||
:return: int count of sell orders that have been filled for this trade.
|
||||
"""
|
||||
return len(self.select_filled_orders('sell'))
|
||||
|
@ -261,11 +261,11 @@ class RPC:
|
||||
profit_str
|
||||
]
|
||||
if self._config.get('position_adjustment_enable', False):
|
||||
max_buy_str = ''
|
||||
max_entry_str = ''
|
||||
if self._config.get('max_entry_position_adjustment', -1) > 0:
|
||||
max_buy_str = f"/{self._config['max_entry_position_adjustment'] + 1}"
|
||||
filled_buys = trade.nr_of_successful_buys
|
||||
detail_trade.append(f"{filled_buys}{max_buy_str}")
|
||||
max_entry_str = f"/{self._config['max_entry_position_adjustment'] + 1}"
|
||||
filled_entries = trade.nr_of_successful_entries
|
||||
detail_trade.append(f"{filled_entries}{max_entry_str}")
|
||||
trades_list.append(detail_trade)
|
||||
profitcol = "Profit"
|
||||
if self._fiat_converter:
|
||||
@ -696,19 +696,18 @@ class RPC:
|
||||
if trade.open_order_id:
|
||||
order = self._freqtrade.exchange.fetch_order(trade.open_order_id, trade.pair)
|
||||
|
||||
if order['side'] == 'buy':
|
||||
if order['side'] == trade.enter_side:
|
||||
fully_canceled = self._freqtrade.handle_cancel_enter(
|
||||
trade, order, CANCEL_REASON['FORCE_SELL'])
|
||||
|
||||
if order['side'] == 'sell':
|
||||
if order['side'] == trade.exit_side:
|
||||
# Cancel order - so it is placed anew with a fresh price.
|
||||
self._freqtrade.handle_cancel_exit(trade, order, CANCEL_REASON['FORCE_SELL'])
|
||||
|
||||
if not fully_canceled:
|
||||
# Get current rate and execute sell
|
||||
closing_side = "buy" if trade.is_short else "sell"
|
||||
current_rate = self._freqtrade.exchange.get_rate(
|
||||
trade.pair, refresh=False, side=closing_side)
|
||||
trade.pair, refresh=False, side=trade.exit_side)
|
||||
sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL)
|
||||
order_type = ordertype or self._freqtrade.strategy.order_types.get(
|
||||
"forcesell", self._freqtrade.strategy.order_types["sell"])
|
||||
@ -769,8 +768,10 @@ class RPC:
|
||||
# check if valid pair
|
||||
|
||||
# check if pair already has an open pair
|
||||
trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first()
|
||||
trade: Trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first()
|
||||
is_short = (order_side == SignalDirection.SHORT)
|
||||
if trade:
|
||||
is_short = trade.is_short
|
||||
if not self._freqtrade.strategy.position_adjustment_enable:
|
||||
raise RPCException(f'position for {pair} already open - id: {trade.id}')
|
||||
|
||||
@ -784,7 +785,7 @@ class RPC:
|
||||
'forcebuy', self._freqtrade.strategy.order_types['buy'])
|
||||
if self._freqtrade.execute_entry(pair, stake_amount, price,
|
||||
ordertype=order_type, trade=trade,
|
||||
is_short=(order_side == SignalDirection.SHORT),
|
||||
is_short=is_short,
|
||||
enter_tag=enter_tag,
|
||||
):
|
||||
Trade.commit()
|
||||
|
@ -634,7 +634,7 @@ tc39 = BTContainer(data=[
|
||||
[3, 5010, 5010, 4986, 5010, 6172, 0, 1],
|
||||
[4, 5010, 5010, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on
|
||||
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
|
||||
stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002 * 5.0, use_sell_signal=True,
|
||||
stop_loss=-0.05, roi={"0": 1}, profit_perc=0.002 * 5.0, use_sell_signal=True,
|
||||
leverage=5.0,
|
||||
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
|
||||
)
|
||||
|
@ -21,7 +21,6 @@ from freqtrade.data.converter import clean_ohlcv_dataframe
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.data.history import get_timerange
|
||||
from freqtrade.enums import RunMode, SellType
|
||||
from freqtrade.enums.tradingmode import TradingMode
|
||||
from freqtrade.exceptions import DependencyException, OperationalException
|
||||
from freqtrade.exchange.exchange import timeframe_to_next_date
|
||||
from freqtrade.misc import get_strategy_run_id
|
||||
|
@ -183,7 +183,7 @@ class StrategyTestV3(IStrategy):
|
||||
current_profit: float, min_stake: float, max_stake: float, **kwargs):
|
||||
|
||||
if current_profit < -0.0075:
|
||||
orders = trade.select_filled_orders('buy')
|
||||
orders = trade.select_filled_orders(trade.enter_side)
|
||||
return round(orders[0].cost, 0)
|
||||
|
||||
return None
|
||||
|
@ -231,13 +231,13 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
|
||||
assert len(Trade.get_trades().all()) == 1
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 1
|
||||
assert trade.stake_amount == 60
|
||||
assert pytest.approx(trade.stake_amount) == 60
|
||||
assert trade.open_rate == 2.0
|
||||
# No adjustment
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 1
|
||||
assert trade.stake_amount == 60
|
||||
assert pytest.approx(trade.stake_amount) == 60
|
||||
|
||||
# Reduce bid amount
|
||||
ticker_usdt_modif = ticker_usdt.return_value
|
||||
@ -266,6 +266,7 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
|
||||
|
||||
assert trade.amount == trade.orders[0].amount + trade.orders[1].amount
|
||||
assert trade.nr_of_successful_buys == 2
|
||||
assert trade.nr_of_successful_entries == 2
|
||||
|
||||
# Sell
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
|
||||
@ -280,3 +281,75 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
|
||||
assert trade.orders[2].amount == trade.amount
|
||||
|
||||
assert trade.nr_of_successful_buys == 2
|
||||
assert trade.nr_of_successful_entries == 2
|
||||
|
||||
|
||||
def test_dca_short(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
|
||||
default_conf_usdt['position_adjustment_enable'] = True
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker_usdt,
|
||||
get_fee=fee,
|
||||
amount_to_precision=lambda s, x, y: y,
|
||||
price_to_precision=lambda s, x, y: y,
|
||||
)
|
||||
|
||||
patch_get_signal(freqtrade, enter_long=False, enter_short=True)
|
||||
freqtrade.enter_positions()
|
||||
|
||||
assert len(Trade.get_trades().all()) == 1
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 1
|
||||
assert pytest.approx(trade.stake_amount) == 60
|
||||
assert trade.open_rate == 2.02
|
||||
# No adjustment
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 1
|
||||
assert pytest.approx(trade.stake_amount) == 60
|
||||
|
||||
# Reduce bid amount
|
||||
ticker_usdt_modif = ticker_usdt.return_value
|
||||
ticker_usdt_modif['ask'] = ticker_usdt_modif['ask'] * 1.015
|
||||
ticker_usdt_modif['bid'] = ticker_usdt_modif['bid'] * 1.0125
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value=ticker_usdt_modif)
|
||||
|
||||
# additional buy order
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 2
|
||||
for o in trade.orders:
|
||||
assert o.status == "closed"
|
||||
assert pytest.approx(trade.stake_amount) == 120
|
||||
|
||||
# Open-rate averaged between 2.0 and 2.0 * 1.015
|
||||
assert trade.open_rate >= 2.02
|
||||
assert trade.open_rate < 2.02 * 1.015
|
||||
|
||||
# No action - profit raised above 1% (the bar set in the strategy).
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 2
|
||||
assert pytest.approx(trade.stake_amount) == 120
|
||||
# assert trade.orders[0].amount == 30
|
||||
assert trade.orders[1].amount == 60 / ticker_usdt_modif['ask']
|
||||
|
||||
assert trade.amount == trade.orders[0].amount + trade.orders[1].amount
|
||||
assert trade.nr_of_successful_entries == 2
|
||||
|
||||
# Buy
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_short=True)
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert trade.is_open is False
|
||||
# assert trade.orders[0].amount == 30
|
||||
assert trade.orders[0].side == 'sell'
|
||||
assert trade.orders[1].amount == 60 / ticker_usdt_modif['ask']
|
||||
# Sold everything
|
||||
assert trade.orders[-1].side == 'buy'
|
||||
assert trade.orders[2].amount == trade.amount
|
||||
|
||||
assert trade.nr_of_successful_entries == 2
|
||||
assert trade.nr_of_successful_exits == 1
|
||||
|
@ -1749,6 +1749,67 @@ def test_stoploss_reinitialization(default_conf, fee):
|
||||
assert trade_adj.initial_stop_loss_pct == -0.04
|
||||
|
||||
|
||||
def test_stoploss_reinitialization_leverage(default_conf, fee):
|
||||
init_db(default_conf['db_url'])
|
||||
trade = Trade(
|
||||
pair='ADA/USDT',
|
||||
stake_amount=30.0,
|
||||
fee_open=fee.return_value,
|
||||
open_date=arrow.utcnow().shift(hours=-2).datetime,
|
||||
amount=30.0,
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance',
|
||||
open_rate=1,
|
||||
max_rate=1,
|
||||
leverage=5.0,
|
||||
)
|
||||
|
||||
trade.adjust_stop_loss(trade.open_rate, 0.1, True)
|
||||
assert trade.stop_loss == 0.98
|
||||
assert trade.stop_loss_pct == -0.1
|
||||
assert trade.initial_stop_loss == 0.98
|
||||
assert trade.initial_stop_loss_pct == -0.1
|
||||
Trade.query.session.add(trade)
|
||||
|
||||
# Lower stoploss
|
||||
Trade.stoploss_reinitialization(0.15)
|
||||
|
||||
trades = Trade.get_open_trades()
|
||||
assert len(trades) == 1
|
||||
trade_adj = trades[0]
|
||||
assert trade_adj.stop_loss == 0.97
|
||||
assert trade_adj.stop_loss_pct == -0.15
|
||||
assert trade_adj.initial_stop_loss == 0.97
|
||||
assert trade_adj.initial_stop_loss_pct == -0.15
|
||||
|
||||
# Raise stoploss
|
||||
Trade.stoploss_reinitialization(0.05)
|
||||
|
||||
trades = Trade.get_open_trades()
|
||||
assert len(trades) == 1
|
||||
trade_adj = trades[0]
|
||||
assert trade_adj.stop_loss == 0.99
|
||||
assert trade_adj.stop_loss_pct == -0.05
|
||||
assert trade_adj.initial_stop_loss == 0.99
|
||||
assert trade_adj.initial_stop_loss_pct == -0.05
|
||||
|
||||
# Trailing stoploss (move stoplos up a bit)
|
||||
trade.adjust_stop_loss(1.02, 0.05)
|
||||
assert trade_adj.stop_loss == 1.0098
|
||||
assert trade_adj.initial_stop_loss == 0.99
|
||||
|
||||
Trade.stoploss_reinitialization(0.05)
|
||||
|
||||
trades = Trade.get_open_trades()
|
||||
assert len(trades) == 1
|
||||
trade_adj = trades[0]
|
||||
# Stoploss should not change in this case.
|
||||
assert trade_adj.stop_loss == 1.0098
|
||||
assert trade_adj.stop_loss_pct == -0.05
|
||||
assert trade_adj.initial_stop_loss == 0.99
|
||||
assert trade_adj.initial_stop_loss_pct == -0.05
|
||||
|
||||
|
||||
def test_stoploss_reinitialization_short(default_conf, fee):
|
||||
init_db(default_conf['db_url'])
|
||||
trade = Trade(
|
||||
@ -1762,50 +1823,50 @@ def test_stoploss_reinitialization_short(default_conf, fee):
|
||||
open_rate=1,
|
||||
max_rate=1,
|
||||
is_short=True,
|
||||
leverage=3.0,
|
||||
leverage=5.0,
|
||||
)
|
||||
trade.adjust_stop_loss(trade.open_rate, -0.05, True)
|
||||
assert trade.stop_loss == 1.05
|
||||
assert trade.stop_loss_pct == 0.05
|
||||
assert trade.initial_stop_loss == 1.05
|
||||
assert trade.initial_stop_loss_pct == 0.05
|
||||
trade.adjust_stop_loss(trade.open_rate, -0.1, True)
|
||||
assert trade.stop_loss == 1.02
|
||||
assert trade.stop_loss_pct == 0.1
|
||||
assert trade.initial_stop_loss == 1.02
|
||||
assert trade.initial_stop_loss_pct == 0.1
|
||||
Trade.query.session.add(trade)
|
||||
# Lower stoploss
|
||||
Trade.stoploss_reinitialization(-0.06)
|
||||
Trade.stoploss_reinitialization(-0.15)
|
||||
trades = Trade.get_open_trades()
|
||||
assert len(trades) == 1
|
||||
trade_adj = trades[0]
|
||||
assert trade_adj.stop_loss == 1.06
|
||||
assert trade_adj.stop_loss_pct == 0.06
|
||||
assert trade_adj.initial_stop_loss == 1.06
|
||||
assert trade_adj.initial_stop_loss_pct == 0.06
|
||||
assert trade_adj.stop_loss == 1.03
|
||||
assert trade_adj.stop_loss_pct == 0.15
|
||||
assert trade_adj.initial_stop_loss == 1.03
|
||||
assert trade_adj.initial_stop_loss_pct == 0.15
|
||||
# Raise stoploss
|
||||
Trade.stoploss_reinitialization(-0.04)
|
||||
Trade.stoploss_reinitialization(-0.05)
|
||||
trades = Trade.get_open_trades()
|
||||
assert len(trades) == 1
|
||||
trade_adj = trades[0]
|
||||
assert trade_adj.stop_loss == 1.04
|
||||
assert trade_adj.stop_loss_pct == 0.04
|
||||
assert trade_adj.initial_stop_loss == 1.04
|
||||
assert trade_adj.initial_stop_loss_pct == 0.04
|
||||
assert trade_adj.stop_loss == 1.01
|
||||
assert trade_adj.stop_loss_pct == 0.05
|
||||
assert trade_adj.initial_stop_loss == 1.01
|
||||
assert trade_adj.initial_stop_loss_pct == 0.05
|
||||
# Trailing stoploss
|
||||
trade.adjust_stop_loss(0.98, -0.04)
|
||||
assert trade_adj.stop_loss == 1.0192
|
||||
assert trade_adj.initial_stop_loss == 1.04
|
||||
Trade.stoploss_reinitialization(-0.04)
|
||||
trade.adjust_stop_loss(0.98, -0.05)
|
||||
assert trade_adj.stop_loss == 0.9898
|
||||
assert trade_adj.initial_stop_loss == 1.01
|
||||
Trade.stoploss_reinitialization(-0.05)
|
||||
trades = Trade.get_open_trades()
|
||||
assert len(trades) == 1
|
||||
trade_adj = trades[0]
|
||||
# Stoploss should not change in this case.
|
||||
assert trade_adj.stop_loss == 1.0192
|
||||
assert trade_adj.stop_loss_pct == 0.04
|
||||
assert trade_adj.initial_stop_loss == 1.04
|
||||
assert trade_adj.initial_stop_loss_pct == 0.04
|
||||
assert trade_adj.stop_loss == 0.9898
|
||||
assert trade_adj.stop_loss_pct == 0.05
|
||||
assert trade_adj.initial_stop_loss == 1.01
|
||||
assert trade_adj.initial_stop_loss_pct == 0.05
|
||||
# Stoploss can't go above liquidation price
|
||||
trade_adj.set_isolated_liq(1.0)
|
||||
trade.adjust_stop_loss(0.97, -0.04)
|
||||
assert trade_adj.stop_loss == 1.0
|
||||
assert trade_adj.stop_loss == 1.0
|
||||
trade_adj.set_isolated_liq(0.985)
|
||||
trade.adjust_stop_loss(0.9799, -0.05)
|
||||
assert trade_adj.stop_loss == 0.985
|
||||
assert trade_adj.stop_loss == 0.985
|
||||
|
||||
|
||||
def test_update_fee(fee):
|
||||
@ -2310,13 +2371,16 @@ def test_recalc_trade_from_orders(fee):
|
||||
assert pytest.approx(trade.open_trade_value) == o1_trade_val + o2_trade_val + o3_trade_val
|
||||
|
||||
|
||||
def test_recalc_trade_from_orders_ignores_bad_orders(fee):
|
||||
@pytest.mark.parametrize('is_short', [True, False])
|
||||
def test_recalc_trade_from_orders_ignores_bad_orders(fee, is_short):
|
||||
|
||||
o1_amount = 100
|
||||
o1_rate = 1
|
||||
o1_cost = o1_amount * o1_rate
|
||||
o1_fee_cost = o1_cost * fee.return_value
|
||||
o1_trade_val = o1_cost + o1_fee_cost
|
||||
o1_trade_val = o1_cost - o1_fee_cost if is_short else o1_cost + o1_fee_cost
|
||||
enter_side = "sell" if is_short else "buy"
|
||||
exit_side = "buy" if is_short else "sell"
|
||||
|
||||
trade = Trade(
|
||||
pair='ADA/USDT',
|
||||
@ -2328,17 +2392,18 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee):
|
||||
exchange='binance',
|
||||
open_rate=o1_rate,
|
||||
max_rate=o1_rate,
|
||||
is_short=is_short,
|
||||
)
|
||||
trade.update_fee(o1_fee_cost, 'BNB', fee.return_value, 'buy')
|
||||
trade.update_fee(o1_fee_cost, 'BNB', fee.return_value, enter_side)
|
||||
# Check with 1 order
|
||||
order1 = Order(
|
||||
ft_order_side='buy',
|
||||
ft_order_side=enter_side,
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=False,
|
||||
status="closed",
|
||||
symbol=trade.pair,
|
||||
order_type="market",
|
||||
side="buy",
|
||||
side=enter_side,
|
||||
price=o1_rate,
|
||||
average=o1_rate,
|
||||
filled=o1_amount,
|
||||
@ -2356,16 +2421,16 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee):
|
||||
assert trade.open_rate == o1_rate
|
||||
assert trade.fee_open_cost == o1_fee_cost
|
||||
assert trade.open_trade_value == o1_trade_val
|
||||
assert trade.nr_of_successful_buys == 1
|
||||
assert trade.nr_of_successful_entries == 1
|
||||
|
||||
order2 = Order(
|
||||
ft_order_side='buy',
|
||||
ft_order_side=enter_side,
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=True,
|
||||
status="open",
|
||||
symbol=trade.pair,
|
||||
order_type="market",
|
||||
side="buy",
|
||||
side=enter_side,
|
||||
price=o1_rate,
|
||||
average=o1_rate,
|
||||
filled=o1_amount,
|
||||
@ -2383,17 +2448,17 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee):
|
||||
assert trade.open_rate == o1_rate
|
||||
assert trade.fee_open_cost == o1_fee_cost
|
||||
assert trade.open_trade_value == o1_trade_val
|
||||
assert trade.nr_of_successful_buys == 1
|
||||
assert trade.nr_of_successful_entries == 1
|
||||
|
||||
# Let's try with some other orders
|
||||
order3 = Order(
|
||||
ft_order_side='buy',
|
||||
ft_order_side=enter_side,
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=False,
|
||||
status="cancelled",
|
||||
symbol=trade.pair,
|
||||
order_type="market",
|
||||
side="buy",
|
||||
side=enter_side,
|
||||
price=1,
|
||||
average=2,
|
||||
filled=0,
|
||||
@ -2411,16 +2476,16 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee):
|
||||
assert trade.open_rate == o1_rate
|
||||
assert trade.fee_open_cost == o1_fee_cost
|
||||
assert trade.open_trade_value == o1_trade_val
|
||||
assert trade.nr_of_successful_buys == 1
|
||||
assert trade.nr_of_successful_entries == 1
|
||||
|
||||
order4 = Order(
|
||||
ft_order_side='buy',
|
||||
ft_order_side=enter_side,
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=False,
|
||||
status="closed",
|
||||
symbol=trade.pair,
|
||||
order_type="market",
|
||||
side="buy",
|
||||
side=enter_side,
|
||||
price=o1_rate,
|
||||
average=o1_rate,
|
||||
filled=o1_amount,
|
||||
@ -2438,17 +2503,17 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee):
|
||||
assert trade.open_rate == o1_rate
|
||||
assert trade.fee_open_cost == 2 * o1_fee_cost
|
||||
assert trade.open_trade_value == 2 * o1_trade_val
|
||||
assert trade.nr_of_successful_buys == 2
|
||||
assert trade.nr_of_successful_entries == 2
|
||||
|
||||
# Just to make sure sell orders are ignored, let's calculate one more time.
|
||||
# Just to make sure exit orders are ignored, let's calculate one more time.
|
||||
sell1 = Order(
|
||||
ft_order_side='sell',
|
||||
ft_order_side=exit_side,
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=False,
|
||||
status="closed",
|
||||
symbol=trade.pair,
|
||||
order_type="market",
|
||||
side="sell",
|
||||
side=exit_side,
|
||||
price=4,
|
||||
average=3,
|
||||
filled=2,
|
||||
@ -2465,16 +2530,17 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee):
|
||||
assert trade.open_rate == o1_rate
|
||||
assert trade.fee_open_cost == 2 * o1_fee_cost
|
||||
assert trade.open_trade_value == 2 * o1_trade_val
|
||||
assert trade.nr_of_successful_buys == 2
|
||||
assert trade.nr_of_successful_entries == 2
|
||||
|
||||
# Check with 1 order
|
||||
order_noavg = Order(
|
||||
ft_order_side='buy',
|
||||
ft_order_side=enter_side,
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=False,
|
||||
status="closed",
|
||||
symbol=trade.pair,
|
||||
order_type="market",
|
||||
side="buy",
|
||||
side=enter_side,
|
||||
price=o1_rate,
|
||||
average=None,
|
||||
filled=o1_amount,
|
||||
@ -2492,7 +2558,7 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee):
|
||||
assert trade.open_rate == o1_rate
|
||||
assert trade.fee_open_cost == 3 * o1_fee_cost
|
||||
assert trade.open_trade_value == 3 * o1_trade_val
|
||||
assert trade.nr_of_successful_buys == 3
|
||||
assert trade.nr_of_successful_entries == 3
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
|
Loading…
Reference in New Issue
Block a user