Merge branch 'feat/short' into pr/samgermain/6467

This commit is contained in:
Matthias 2022-02-28 20:07:19 +01:00
commit 1b07ad92cf
10 changed files with 290 additions and 107 deletions

View File

@ -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`. 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. `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" !!! Note "About stake size"
Using fixed stake size means it will be the amount used for the first order, just like without position adjustment. 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. 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 return None
filled_buys = trade.select_filled_orders('buy') 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) # Allow up to 3 additional increasingly larger buys (4 in total)
# Initial buy is 1x # Initial buy is 1x
# If that falls to -5% profit, we buy 1.25x more, average profit should increase to roughly -2.2% # 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 # This returns first order stake size
stake_amount = filled_buys[0].cost stake_amount = filled_buys[0].cost
# This then calculates current safety order size # 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 return stake_amount
except Exception as exception: except Exception as exception:
return None return None

View File

@ -103,7 +103,6 @@ class FreqtradeBot(LoggingMixin):
self._exit_lock = Lock() self._exit_lock = Lock()
LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe)) 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.trading_mode: TradingMode = self.config.get('trading_mode', TradingMode.SPOT)
self.margin_mode_type: Optional[MarginMode] = None self.margin_mode_type: Optional[MarginMode] = None
if 'margin_mode' in self.config: 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. # TODO-lev: Check what changes are necessary for DCA in relation to shorts.
if self.strategy.max_entry_position_adjustment > -1: 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: if count_of_buys > self.strategy.max_entry_position_adjustment:
logger.debug(f"Max adjustment entries for {trade.pair} has been reached.") logger.debug(f"Max adjustment entries for {trade.pair} has been reached.")
return return
@ -533,7 +532,7 @@ class FreqtradeBot(LoggingMixin):
if stake_amount is not None and stake_amount > 0.0: if stake_amount is not None and stake_amount > 0.0:
# We should increase our position # 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: if stake_amount is not None and stake_amount < 0.0:
# We should decrease our position # We should decrease our position
@ -607,7 +606,7 @@ class FreqtradeBot(LoggingMixin):
if not stake_amount: if not stake_amount:
return False return False
if not pos_adjust:
max_leverage = self.exchange.get_max_leverage(pair, stake_amount) max_leverage = self.exchange.get_max_leverage(pair, stake_amount)
leverage = strategy_safe_wrapper(self.strategy.leverage, default_retval=1.0)( leverage = strategy_safe_wrapper(self.strategy.leverage, default_retval=1.0)(
pair=pair, pair=pair,
@ -619,6 +618,9 @@ class FreqtradeBot(LoggingMixin):
) if self.trading_mode != TradingMode.SPOT else 1.0 ) if self.trading_mode != TradingMode.SPOT else 1.0
# Cap leverage between 1.0 and max_leverage. # Cap leverage between 1.0 and max_leverage.
leverage = min(max(leverage, 1.0), 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: if pos_adjust:
logger.info(f"Position adjust: about to create a new order for {pair} with stake: " logger.info(f"Position adjust: about to create a new order for {pair} with stake: "
f"{stake_amount} for {trade}") f"{stake_amount} for {trade}")
@ -688,6 +690,7 @@ class FreqtradeBot(LoggingMixin):
amount = safe_value_fallback(order, 'filled', 'amount') amount = safe_value_fallback(order, 'filled', 'amount')
enter_limit_filled_price = safe_value_fallback(order, 'average', 'price') 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( isolated_liq = self.exchange.get_liquidation_price(
leverage=leverage, leverage=leverage,
pair=pair, 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 enter order is fulfilled but there is no stoploss, we add a stoploss on exchange
if not stoploss_order: 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: if trade.is_short:
stop_price = trade.open_rate * (1 - stoploss) stop_price = trade.open_rate * (1 - stoploss)
else: else:
@ -1557,9 +1564,20 @@ class FreqtradeBot(LoggingMixin):
Trade.commit() Trade.commit()
if order['status'] in constants.NON_OPEN_EXCHANGE_STATES: if order['status'] in constants.NON_OPEN_EXCHANGE_STATES:
# If a buy order was closed, force update on stoploss on exchange # If a entry order was closed, force update on stoploss on exchange
if order.get('side', None) == 'buy': if order.get('side', None) == trade.enter_side:
trade = self.cancel_stoploss_on_exchange(trade) 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 # Updating wallets when order is closed
self.wallets.update() self.wallets.update()
@ -1568,7 +1586,7 @@ class FreqtradeBot(LoggingMixin):
self._notify_exit(trade, '', True) self._notify_exit(trade, '', True)
self.handle_protections(trade.pair) self.handle_protections(trade.pair)
elif send_msg and not trade.open_order_id: elif send_msg and not trade.open_order_id:
# Buy fill # Enter fill
self._notify_enter(trade, order, fill=True) self._notify_enter(trade, order, fill=True)
return False return False

View File

@ -465,11 +465,11 @@ class Backtesting:
# Check if we need to adjust our current positions # Check if we need to adjust our current positions
if self.strategy.position_adjustment_enable: if self.strategy.position_adjustment_enable:
check_adjust_buy = True check_adjust_entry = True
if self.strategy.max_entry_position_adjustment > -1: if self.strategy.max_entry_position_adjustment > -1:
count_of_buys = trade.nr_of_successful_buys entry_count = trade.nr_of_successful_entries
check_adjust_buy = (count_of_buys <= self.strategy.max_entry_position_adjustment) check_adjust_entry = (entry_count <= self.strategy.max_entry_position_adjustment)
if check_adjust_buy: if check_adjust_entry:
trade = self._get_adjust_trade_entry_for_candle(trade, sell_row) trade = self._get_adjust_trade_entry_for_candle(trade, sell_row)
sell_candle_time: datetime = sell_row[DATE_IDX].to_pydatetime() sell_candle_time: datetime = sell_row[DATE_IDX].to_pydatetime()
@ -640,6 +640,7 @@ class Backtesting:
# If not pos adjust, trade is None # If not pos adjust, trade is None
return trade return trade
if not pos_adjust:
max_leverage = self.exchange.get_max_leverage(pair, stake_amount) max_leverage = self.exchange.get_max_leverage(pair, stake_amount)
leverage = strategy_safe_wrapper(self.strategy.leverage, default_retval=1.0)( leverage = strategy_safe_wrapper(self.strategy.leverage, default_retval=1.0)(
pair=pair, pair=pair,
@ -651,6 +652,8 @@ class Backtesting:
) if self._can_short else 1.0 ) if self._can_short else 1.0
# Cap leverage between 1.0 and max_leverage. # Cap leverage between 1.0 and max_leverage.
leverage = min(max(leverage, 1.0), 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'] order_type = self.strategy.order_types['buy']
time_in_force = self.strategy.order_time_in_force['buy'] time_in_force = self.strategy.order_time_in_force['buy']
@ -745,7 +748,7 @@ class Backtesting:
for pair in open_trades.keys(): for pair in open_trades.keys():
if len(open_trades[pair]) > 0: if len(open_trades[pair]) > 0:
for trade in open_trades[pair]: 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 # Ignore trade if buy-order did not fill yet
continue continue
sell_row = data[pair][-1] sell_row = data[pair][-1]
@ -798,7 +801,7 @@ class Backtesting:
if timedout: if timedout:
if order.side == 'buy': if order.side == 'buy':
self.timedout_entry_orders += 1 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. # Remove trade due to buy timeout expiration.
return True return True
else: else:

View File

@ -568,13 +568,14 @@ class LocalTrade():
# Don't modify if called with initial and nothing to do # Don't modify if called with initial and nothing to do
return return
leverage = self.leverage or 1.0
if self.is_short: 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 trading with leverage, don't set the stoploss below the liquidation price
if self.isolated_liq: if self.isolated_liq:
new_loss = min(self.isolated_liq, new_loss) new_loss = min(self.isolated_liq, new_loss)
else: 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 trading with leverage, don't set the stoploss below the liquidation price
if self.isolated_liq: if self.isolated_liq:
new_loss = max(self.isolated_liq, new_loss) new_loss = max(self.isolated_liq, new_loss)
@ -868,7 +869,7 @@ class LocalTrade():
def recalc_trade_from_orders(self): def recalc_trade_from_orders(self):
# We need at least 2 entry orders for averaging amounts and rates. # 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 # Just in case, still recalc open trade value
self.recalc_open_trade_value() self.recalc_open_trade_value()
return return
@ -890,8 +891,9 @@ class LocalTrade():
total_stake += tmp_price * tmp_amount total_stake += tmp_price * tmp_amount
if total_amount > 0: 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.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.amount = total_amount
self.fee_open_cost = self.fee_open * self.stake_amount self.fee_open_cost = self.fee_open * self.stake_amount
self.recalc_open_trade_value() self.recalc_open_trade_value()
@ -937,10 +939,28 @@ class LocalTrade():
(o.filled or 0) > 0 and (o.filled or 0) > 0 and
o.status in NON_OPEN_EXCHANGE_STATES] 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 @property
def nr_of_successful_buys(self) -> int: def nr_of_successful_buys(self) -> int:
""" """
Helper function to count the number of buy orders that have been filled. 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. :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: def nr_of_successful_sells(self) -> int:
""" """
Helper function to count the number of sell orders that have been filled. 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: int count of sell orders that have been filled for this trade.
""" """
return len(self.select_filled_orders('sell')) return len(self.select_filled_orders('sell'))

View File

@ -261,11 +261,11 @@ class RPC:
profit_str profit_str
] ]
if self._config.get('position_adjustment_enable', False): if self._config.get('position_adjustment_enable', False):
max_buy_str = '' max_entry_str = ''
if self._config.get('max_entry_position_adjustment', -1) > 0: if self._config.get('max_entry_position_adjustment', -1) > 0:
max_buy_str = f"/{self._config['max_entry_position_adjustment'] + 1}" max_entry_str = f"/{self._config['max_entry_position_adjustment'] + 1}"
filled_buys = trade.nr_of_successful_buys filled_entries = trade.nr_of_successful_entries
detail_trade.append(f"{filled_buys}{max_buy_str}") detail_trade.append(f"{filled_entries}{max_entry_str}")
trades_list.append(detail_trade) trades_list.append(detail_trade)
profitcol = "Profit" profitcol = "Profit"
if self._fiat_converter: if self._fiat_converter:
@ -696,19 +696,18 @@ class RPC:
if trade.open_order_id: if trade.open_order_id:
order = self._freqtrade.exchange.fetch_order(trade.open_order_id, trade.pair) 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( fully_canceled = self._freqtrade.handle_cancel_enter(
trade, order, CANCEL_REASON['FORCE_SELL']) 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. # Cancel order - so it is placed anew with a fresh price.
self._freqtrade.handle_cancel_exit(trade, order, CANCEL_REASON['FORCE_SELL']) self._freqtrade.handle_cancel_exit(trade, order, CANCEL_REASON['FORCE_SELL'])
if not fully_canceled: if not fully_canceled:
# Get current rate and execute sell # Get current rate and execute sell
closing_side = "buy" if trade.is_short else "sell"
current_rate = self._freqtrade.exchange.get_rate( 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) sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL)
order_type = ordertype or self._freqtrade.strategy.order_types.get( order_type = ordertype or self._freqtrade.strategy.order_types.get(
"forcesell", self._freqtrade.strategy.order_types["sell"]) "forcesell", self._freqtrade.strategy.order_types["sell"])
@ -769,8 +768,10 @@ class RPC:
# check if valid pair # check if valid pair
# check if pair already has an open 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: if trade:
is_short = trade.is_short
if not self._freqtrade.strategy.position_adjustment_enable: if not self._freqtrade.strategy.position_adjustment_enable:
raise RPCException(f'position for {pair} already open - id: {trade.id}') raise RPCException(f'position for {pair} already open - id: {trade.id}')
@ -784,7 +785,7 @@ class RPC:
'forcebuy', self._freqtrade.strategy.order_types['buy']) 'forcebuy', self._freqtrade.strategy.order_types['buy'])
if self._freqtrade.execute_entry(pair, stake_amount, price, if self._freqtrade.execute_entry(pair, stake_amount, price,
ordertype=order_type, trade=trade, ordertype=order_type, trade=trade,
is_short=(order_side == SignalDirection.SHORT), is_short=is_short,
enter_tag=enter_tag, enter_tag=enter_tag,
): ):
Trade.commit() Trade.commit()

View File

@ -634,7 +634,7 @@ tc39 = BTContainer(data=[
[3, 5010, 5010, 4986, 5010, 6172, 0, 1], [3, 5010, 5010, 4986, 5010, 6172, 0, 1],
[4, 5010, 5010, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on [4, 5010, 5010, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]], [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, leverage=5.0,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)] trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
) )

View File

@ -21,7 +21,6 @@ from freqtrade.data.converter import clean_ohlcv_dataframe
from freqtrade.data.dataprovider import DataProvider from freqtrade.data.dataprovider import DataProvider
from freqtrade.data.history import get_timerange from freqtrade.data.history import get_timerange
from freqtrade.enums import RunMode, SellType from freqtrade.enums import RunMode, SellType
from freqtrade.enums.tradingmode import TradingMode
from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.exchange.exchange import timeframe_to_next_date from freqtrade.exchange.exchange import timeframe_to_next_date
from freqtrade.misc import get_strategy_run_id from freqtrade.misc import get_strategy_run_id

View File

@ -183,7 +183,7 @@ class StrategyTestV3(IStrategy):
current_profit: float, min_stake: float, max_stake: float, **kwargs): current_profit: float, min_stake: float, max_stake: float, **kwargs):
if current_profit < -0.0075: 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 round(orders[0].cost, 0)
return None return None

View File

@ -231,13 +231,13 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
assert len(Trade.get_trades().all()) == 1 assert len(Trade.get_trades().all()) == 1
trade = Trade.get_trades().first() trade = Trade.get_trades().first()
assert len(trade.orders) == 1 assert len(trade.orders) == 1
assert trade.stake_amount == 60 assert pytest.approx(trade.stake_amount) == 60
assert trade.open_rate == 2.0 assert trade.open_rate == 2.0
# No adjustment # No adjustment
freqtrade.process() freqtrade.process()
trade = Trade.get_trades().first() trade = Trade.get_trades().first()
assert len(trade.orders) == 1 assert len(trade.orders) == 1
assert trade.stake_amount == 60 assert pytest.approx(trade.stake_amount) == 60
# Reduce bid amount # Reduce bid amount
ticker_usdt_modif = ticker_usdt.return_value 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.amount == trade.orders[0].amount + trade.orders[1].amount
assert trade.nr_of_successful_buys == 2 assert trade.nr_of_successful_buys == 2
assert trade.nr_of_successful_entries == 2
# Sell # Sell
patch_get_signal(freqtrade, enter_long=False, exit_long=True) 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.orders[2].amount == trade.amount
assert trade.nr_of_successful_buys == 2 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

View File

@ -1749,6 +1749,67 @@ def test_stoploss_reinitialization(default_conf, fee):
assert trade_adj.initial_stop_loss_pct == -0.04 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): def test_stoploss_reinitialization_short(default_conf, fee):
init_db(default_conf['db_url']) init_db(default_conf['db_url'])
trade = Trade( trade = Trade(
@ -1762,50 +1823,50 @@ def test_stoploss_reinitialization_short(default_conf, fee):
open_rate=1, open_rate=1,
max_rate=1, max_rate=1,
is_short=True, is_short=True,
leverage=3.0, leverage=5.0,
) )
trade.adjust_stop_loss(trade.open_rate, -0.05, True) trade.adjust_stop_loss(trade.open_rate, -0.1, True)
assert trade.stop_loss == 1.05 assert trade.stop_loss == 1.02
assert trade.stop_loss_pct == 0.05 assert trade.stop_loss_pct == 0.1
assert trade.initial_stop_loss == 1.05 assert trade.initial_stop_loss == 1.02
assert trade.initial_stop_loss_pct == 0.05 assert trade.initial_stop_loss_pct == 0.1
Trade.query.session.add(trade) Trade.query.session.add(trade)
# Lower stoploss # Lower stoploss
Trade.stoploss_reinitialization(-0.06) Trade.stoploss_reinitialization(-0.15)
trades = Trade.get_open_trades() trades = Trade.get_open_trades()
assert len(trades) == 1 assert len(trades) == 1
trade_adj = trades[0] trade_adj = trades[0]
assert trade_adj.stop_loss == 1.06 assert trade_adj.stop_loss == 1.03
assert trade_adj.stop_loss_pct == 0.06 assert trade_adj.stop_loss_pct == 0.15
assert trade_adj.initial_stop_loss == 1.06 assert trade_adj.initial_stop_loss == 1.03
assert trade_adj.initial_stop_loss_pct == 0.06 assert trade_adj.initial_stop_loss_pct == 0.15
# Raise stoploss # Raise stoploss
Trade.stoploss_reinitialization(-0.04) Trade.stoploss_reinitialization(-0.05)
trades = Trade.get_open_trades() trades = Trade.get_open_trades()
assert len(trades) == 1 assert len(trades) == 1
trade_adj = trades[0] trade_adj = trades[0]
assert trade_adj.stop_loss == 1.04 assert trade_adj.stop_loss == 1.01
assert trade_adj.stop_loss_pct == 0.04 assert trade_adj.stop_loss_pct == 0.05
assert trade_adj.initial_stop_loss == 1.04 assert trade_adj.initial_stop_loss == 1.01
assert trade_adj.initial_stop_loss_pct == 0.04 assert trade_adj.initial_stop_loss_pct == 0.05
# Trailing stoploss # Trailing stoploss
trade.adjust_stop_loss(0.98, -0.04) trade.adjust_stop_loss(0.98, -0.05)
assert trade_adj.stop_loss == 1.0192 assert trade_adj.stop_loss == 0.9898
assert trade_adj.initial_stop_loss == 1.04 assert trade_adj.initial_stop_loss == 1.01
Trade.stoploss_reinitialization(-0.04) Trade.stoploss_reinitialization(-0.05)
trades = Trade.get_open_trades() trades = Trade.get_open_trades()
assert len(trades) == 1 assert len(trades) == 1
trade_adj = trades[0] trade_adj = trades[0]
# Stoploss should not change in this case. # Stoploss should not change in this case.
assert trade_adj.stop_loss == 1.0192 assert trade_adj.stop_loss == 0.9898
assert trade_adj.stop_loss_pct == 0.04 assert trade_adj.stop_loss_pct == 0.05
assert trade_adj.initial_stop_loss == 1.04 assert trade_adj.initial_stop_loss == 1.01
assert trade_adj.initial_stop_loss_pct == 0.04 assert trade_adj.initial_stop_loss_pct == 0.05
# Stoploss can't go above liquidation price # Stoploss can't go above liquidation price
trade_adj.set_isolated_liq(1.0) trade_adj.set_isolated_liq(0.985)
trade.adjust_stop_loss(0.97, -0.04) trade.adjust_stop_loss(0.9799, -0.05)
assert trade_adj.stop_loss == 1.0 assert trade_adj.stop_loss == 0.985
assert trade_adj.stop_loss == 1.0 assert trade_adj.stop_loss == 0.985
def test_update_fee(fee): 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 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_amount = 100
o1_rate = 1 o1_rate = 1
o1_cost = o1_amount * o1_rate o1_cost = o1_amount * o1_rate
o1_fee_cost = o1_cost * fee.return_value 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( trade = Trade(
pair='ADA/USDT', pair='ADA/USDT',
@ -2328,17 +2392,18 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee):
exchange='binance', exchange='binance',
open_rate=o1_rate, open_rate=o1_rate,
max_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 # Check with 1 order
order1 = Order( order1 = Order(
ft_order_side='buy', ft_order_side=enter_side,
ft_pair=trade.pair, ft_pair=trade.pair,
ft_is_open=False, ft_is_open=False,
status="closed", status="closed",
symbol=trade.pair, symbol=trade.pair,
order_type="market", order_type="market",
side="buy", side=enter_side,
price=o1_rate, price=o1_rate,
average=o1_rate, average=o1_rate,
filled=o1_amount, 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.open_rate == o1_rate
assert trade.fee_open_cost == o1_fee_cost assert trade.fee_open_cost == o1_fee_cost
assert trade.open_trade_value == o1_trade_val assert trade.open_trade_value == o1_trade_val
assert trade.nr_of_successful_buys == 1 assert trade.nr_of_successful_entries == 1
order2 = Order( order2 = Order(
ft_order_side='buy', ft_order_side=enter_side,
ft_pair=trade.pair, ft_pair=trade.pair,
ft_is_open=True, ft_is_open=True,
status="open", status="open",
symbol=trade.pair, symbol=trade.pair,
order_type="market", order_type="market",
side="buy", side=enter_side,
price=o1_rate, price=o1_rate,
average=o1_rate, average=o1_rate,
filled=o1_amount, 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.open_rate == o1_rate
assert trade.fee_open_cost == o1_fee_cost assert trade.fee_open_cost == o1_fee_cost
assert trade.open_trade_value == o1_trade_val 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 # Let's try with some other orders
order3 = Order( order3 = Order(
ft_order_side='buy', ft_order_side=enter_side,
ft_pair=trade.pair, ft_pair=trade.pair,
ft_is_open=False, ft_is_open=False,
status="cancelled", status="cancelled",
symbol=trade.pair, symbol=trade.pair,
order_type="market", order_type="market",
side="buy", side=enter_side,
price=1, price=1,
average=2, average=2,
filled=0, filled=0,
@ -2411,16 +2476,16 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee):
assert trade.open_rate == o1_rate assert trade.open_rate == o1_rate
assert trade.fee_open_cost == o1_fee_cost assert trade.fee_open_cost == o1_fee_cost
assert trade.open_trade_value == o1_trade_val assert trade.open_trade_value == o1_trade_val
assert trade.nr_of_successful_buys == 1 assert trade.nr_of_successful_entries == 1
order4 = Order( order4 = Order(
ft_order_side='buy', ft_order_side=enter_side,
ft_pair=trade.pair, ft_pair=trade.pair,
ft_is_open=False, ft_is_open=False,
status="closed", status="closed",
symbol=trade.pair, symbol=trade.pair,
order_type="market", order_type="market",
side="buy", side=enter_side,
price=o1_rate, price=o1_rate,
average=o1_rate, average=o1_rate,
filled=o1_amount, 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.open_rate == o1_rate
assert trade.fee_open_cost == 2 * o1_fee_cost assert trade.fee_open_cost == 2 * o1_fee_cost
assert trade.open_trade_value == 2 * o1_trade_val 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( sell1 = Order(
ft_order_side='sell', ft_order_side=exit_side,
ft_pair=trade.pair, ft_pair=trade.pair,
ft_is_open=False, ft_is_open=False,
status="closed", status="closed",
symbol=trade.pair, symbol=trade.pair,
order_type="market", order_type="market",
side="sell", side=exit_side,
price=4, price=4,
average=3, average=3,
filled=2, filled=2,
@ -2465,16 +2530,17 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee):
assert trade.open_rate == o1_rate assert trade.open_rate == o1_rate
assert trade.fee_open_cost == 2 * o1_fee_cost assert trade.fee_open_cost == 2 * o1_fee_cost
assert trade.open_trade_value == 2 * o1_trade_val 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 # Check with 1 order
order_noavg = Order( order_noavg = Order(
ft_order_side='buy', ft_order_side=enter_side,
ft_pair=trade.pair, ft_pair=trade.pair,
ft_is_open=False, ft_is_open=False,
status="closed", status="closed",
symbol=trade.pair, symbol=trade.pair,
order_type="market", order_type="market",
side="buy", side=enter_side,
price=o1_rate, price=o1_rate,
average=None, average=None,
filled=o1_amount, 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.open_rate == o1_rate
assert trade.fee_open_cost == 3 * o1_fee_cost assert trade.fee_open_cost == 3 * o1_fee_cost
assert trade.open_trade_value == 3 * o1_trade_val 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") @pytest.mark.usefixtures("init_persistence")