Merge branch 'develop' into feature/fetch-public-trades
This commit is contained in:
@@ -22,9 +22,11 @@ from pandas import DataFrame, concat
|
|||||||
from freqtrade.constants import (DEFAULT_TRADES_COLUMNS, DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, BidAsk,
|
from freqtrade.constants import (DEFAULT_TRADES_COLUMNS, DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, BidAsk,
|
||||||
BuySell, Config, EntryExit, ExchangeConfig,
|
BuySell, Config, EntryExit, ExchangeConfig,
|
||||||
ListPairsWithTimeframes, MakerTaker, OBLiteral, PairWithTimeframe)
|
ListPairsWithTimeframes, MakerTaker, OBLiteral, PairWithTimeframe)
|
||||||
|
|
||||||
from freqtrade.data.converter import clean_duplicate_trades, clean_ohlcv_dataframe, ohlcv_to_dataframe, trades_dict_to_list, public_trades_to_dataframe
|
from freqtrade.data.converter import clean_duplicate_trades, clean_ohlcv_dataframe, ohlcv_to_dataframe, trades_dict_to_list, public_trades_to_dataframe
|
||||||
from freqtrade.data.converter.converter import _calculate_ohlcv_candle_start_and_end
|
from freqtrade.data.converter.converter import _calculate_ohlcv_candle_start_and_end
|
||||||
from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, PriceType, TradingMode
|
from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, PriceType, RunMode, TradingMode
|
||||||
|
|
||||||
from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
|
from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
|
||||||
InvalidOrderException, OperationalException, PricingError,
|
InvalidOrderException, OperationalException, PricingError,
|
||||||
RetryableOrderError, TemporaryError)
|
RetryableOrderError, TemporaryError)
|
||||||
@@ -629,7 +631,11 @@ class Exchange:
|
|||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
f"Invalid timeframe '{timeframe}'. This exchange supports: {self.timeframes}")
|
f"Invalid timeframe '{timeframe}'. This exchange supports: {self.timeframes}")
|
||||||
|
|
||||||
if timeframe and timeframe_to_minutes(timeframe) < 1:
|
if (
|
||||||
|
timeframe
|
||||||
|
and self._config['runmode'] != RunMode.UTIL_EXCHANGE
|
||||||
|
and timeframe_to_minutes(timeframe) < 1
|
||||||
|
):
|
||||||
raise OperationalException("Timeframes < 1m are currently not supported by Freqtrade.")
|
raise OperationalException("Timeframes < 1m are currently not supported by Freqtrade.")
|
||||||
|
|
||||||
def validate_ordertypes(self, order_types: Dict) -> None:
|
def validate_ordertypes(self, order_types: Dict) -> None:
|
||||||
|
|||||||
+64
-57
@@ -432,10 +432,6 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
try:
|
try:
|
||||||
fo = self.exchange.fetch_order_or_stoploss_order(order.order_id, order.ft_pair,
|
fo = self.exchange.fetch_order_or_stoploss_order(order.order_id, order.ft_pair,
|
||||||
order.ft_order_side == 'stoploss')
|
order.ft_order_side == 'stoploss')
|
||||||
if order.ft_order_side == 'stoploss':
|
|
||||||
if fo and fo['status'] == 'open':
|
|
||||||
# Assume this as the open stoploss order
|
|
||||||
trade.stoploss_order_id = order.order_id
|
|
||||||
if fo:
|
if fo:
|
||||||
logger.info(f"Found {order} for trade {trade}.")
|
logger.info(f"Found {order} for trade {trade}.")
|
||||||
self.update_trade_state(trade, order.order_id, fo,
|
self.update_trade_state(trade, order.order_id, fo,
|
||||||
@@ -895,17 +891,15 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
|
|
||||||
def cancel_stoploss_on_exchange(self, trade: Trade) -> Trade:
|
def cancel_stoploss_on_exchange(self, trade: Trade) -> Trade:
|
||||||
# First cancelling stoploss on exchange ...
|
# First cancelling stoploss on exchange ...
|
||||||
if trade.stoploss_order_id:
|
for oslo in trade.open_sl_orders:
|
||||||
try:
|
try:
|
||||||
logger.info(f"Cancelling stoploss on exchange for {trade}")
|
logger.info(f"Cancelling stoploss on exchange for {trade} "
|
||||||
|
f"order: {oslo.order_id}")
|
||||||
co = self.exchange.cancel_stoploss_order_with_result(
|
co = self.exchange.cancel_stoploss_order_with_result(
|
||||||
trade.stoploss_order_id, trade.pair, trade.amount)
|
oslo.order_id, trade.pair, trade.amount)
|
||||||
self.update_trade_state(trade, trade.stoploss_order_id, co, stoploss_order=True)
|
self.update_trade_state(trade, oslo.order_id, co, stoploss_order=True)
|
||||||
|
|
||||||
# Reset stoploss order id.
|
|
||||||
trade.stoploss_order_id = None
|
|
||||||
except InvalidOrderException:
|
except InvalidOrderException:
|
||||||
logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id} "
|
logger.exception(f"Could not cancel stoploss order {oslo.order_id} "
|
||||||
f"for pair {trade.pair}")
|
f"for pair {trade.pair}")
|
||||||
return trade
|
return trade
|
||||||
|
|
||||||
@@ -1080,7 +1074,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
not trade.has_open_orders
|
not trade.has_open_orders
|
||||||
and not trade.stoploss_order_id
|
and not trade.has_open_sl_orders
|
||||||
and not self.wallets.check_exit_amount(trade)
|
and not self.wallets.check_exit_amount(trade)
|
||||||
):
|
):
|
||||||
logger.warning(
|
logger.warning(
|
||||||
@@ -1190,8 +1184,6 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
order_obj = Order.parse_from_ccxt_object(stoploss_order, trade.pair, 'stoploss',
|
order_obj = Order.parse_from_ccxt_object(stoploss_order, trade.pair, 'stoploss',
|
||||||
trade.amount, stop_price)
|
trade.amount, stop_price)
|
||||||
trade.orders.append(order_obj)
|
trade.orders.append(order_obj)
|
||||||
trade.stoploss_order_id = str(stoploss_order['id'])
|
|
||||||
trade.stoploss_last_update = datetime.now(timezone.utc)
|
|
||||||
return True
|
return True
|
||||||
except InsufficientFundsError as e:
|
except InsufficientFundsError as e:
|
||||||
logger.warning(f"Unable to place stoploss order {e}.")
|
logger.warning(f"Unable to place stoploss order {e}.")
|
||||||
@@ -1199,13 +1191,11 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
self.handle_insufficient_funds(trade)
|
self.handle_insufficient_funds(trade)
|
||||||
|
|
||||||
except InvalidOrderException as e:
|
except InvalidOrderException as e:
|
||||||
trade.stoploss_order_id = None
|
|
||||||
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
||||||
logger.warning('Exiting the trade forcefully')
|
logger.warning('Exiting the trade forcefully')
|
||||||
self.emergency_exit(trade, stop_price)
|
self.emergency_exit(trade, stop_price)
|
||||||
|
|
||||||
except ExchangeError:
|
except ExchangeError:
|
||||||
trade.stoploss_order_id = None
|
|
||||||
logger.exception('Unable to place a stoploss order on exchange.')
|
logger.exception('Unable to place a stoploss order on exchange.')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -1219,27 +1209,28 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
logger.debug('Handling stoploss on exchange %s ...', trade)
|
logger.debug('Handling stoploss on exchange %s ...', trade)
|
||||||
stoploss_order = None
|
|
||||||
|
|
||||||
try:
|
stoploss_orders = []
|
||||||
# First we check if there is already a stoploss on exchange
|
for slo in trade.open_sl_orders:
|
||||||
stoploss_order = self.exchange.fetch_stoploss_order(
|
stoploss_order = None
|
||||||
trade.stoploss_order_id, trade.pair) if trade.stoploss_order_id else None
|
try:
|
||||||
except InvalidOrderException as exception:
|
# First we check if there is already a stoploss on exchange
|
||||||
logger.warning('Unable to fetch stoploss order: %s', exception)
|
stoploss_order = self.exchange.fetch_stoploss_order(
|
||||||
|
slo.order_id, trade.pair) if slo.order_id else None
|
||||||
|
except InvalidOrderException as exception:
|
||||||
|
logger.warning('Unable to fetch stoploss order: %s', exception)
|
||||||
|
|
||||||
if stoploss_order:
|
if stoploss_order:
|
||||||
self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order,
|
stoploss_orders.append(stoploss_order)
|
||||||
stoploss_order=True)
|
self.update_trade_state(trade, slo.order_id, stoploss_order,
|
||||||
|
stoploss_order=True)
|
||||||
|
|
||||||
# We check if stoploss order is fulfilled
|
# We check if stoploss order is fulfilled
|
||||||
if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'):
|
if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'):
|
||||||
trade.exit_reason = ExitType.STOPLOSS_ON_EXCHANGE.value
|
trade.exit_reason = ExitType.STOPLOSS_ON_EXCHANGE.value
|
||||||
self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order,
|
self._notify_exit(trade, "stoploss", True)
|
||||||
stoploss_order=True)
|
self.handle_protections(trade.pair, trade.trade_direction)
|
||||||
self._notify_exit(trade, "stoploss", True)
|
return True
|
||||||
self.handle_protections(trade.pair, trade.trade_direction)
|
|
||||||
return True
|
|
||||||
|
|
||||||
if trade.has_open_orders or not trade.is_open:
|
if trade.has_open_orders or not trade.is_open:
|
||||||
# Trade has an open Buy or Sell order, Stoploss-handling can't happen in this case
|
# Trade has an open Buy or Sell order, Stoploss-handling can't happen in this case
|
||||||
@@ -1248,7 +1239,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# 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 len(stoploss_orders) == 0:
|
||||||
stop_price = trade.stoploss_or_liquidation
|
stop_price = trade.stoploss_or_liquidation
|
||||||
if self.edge:
|
if self.edge:
|
||||||
stoploss = self.edge.get_stoploss(pair=trade.pair)
|
stoploss = self.edge.get_stoploss(pair=trade.pair)
|
||||||
@@ -1262,27 +1253,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
# in which case the trade will be closed - which we must check below.
|
# in which case the trade will be closed - which we must check below.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# If stoploss order is canceled for some reason we add it again
|
self.manage_trade_stoploss_orders(trade, stoploss_orders)
|
||||||
if (trade.is_open
|
|
||||||
and stoploss_order
|
|
||||||
and stoploss_order['status'] in ('canceled', 'cancelled')):
|
|
||||||
if self.create_stoploss_order(trade=trade, stop_price=trade.stoploss_or_liquidation):
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
logger.warning('Stoploss order was cancelled, but unable to recreate one.')
|
|
||||||
|
|
||||||
# Finally we check if stoploss on exchange should be moved up because of trailing.
|
|
||||||
# Triggered Orders are now real orders - so don't replace stoploss anymore
|
|
||||||
if (
|
|
||||||
trade.is_open and stoploss_order
|
|
||||||
and stoploss_order.get('status_stop') != 'triggered'
|
|
||||||
and (self.config.get('trailing_stop', False)
|
|
||||||
or self.config.get('use_custom_stoploss', False))
|
|
||||||
):
|
|
||||||
# if trailing stoploss is enabled we check if stoploss value has changed
|
|
||||||
# in which case we cancel stoploss order and put another one with new
|
|
||||||
# value immediately
|
|
||||||
self.handle_trailing_stoploss_on_exchange(trade, stoploss_order)
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -1318,6 +1289,42 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
logger.warning(f"Could not create trailing stoploss order "
|
logger.warning(f"Could not create trailing stoploss order "
|
||||||
f"for pair {trade.pair}.")
|
f"for pair {trade.pair}.")
|
||||||
|
|
||||||
|
def manage_trade_stoploss_orders(self, trade: Trade, stoploss_orders: List[Dict]):
|
||||||
|
"""
|
||||||
|
Perform required actions acording to existing stoploss orders of trade
|
||||||
|
:param trade: Corresponding Trade
|
||||||
|
:param stoploss_orders: Current on exchange stoploss orders
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
# If all stoploss orderd are canceled for some reason we add it again
|
||||||
|
canceled_sl_orders = [o for o in stoploss_orders
|
||||||
|
if o['status'] in ('canceled', 'cancelled')]
|
||||||
|
if (
|
||||||
|
trade.is_open and
|
||||||
|
len(stoploss_orders) > 0 and
|
||||||
|
len(stoploss_orders) == len(canceled_sl_orders)
|
||||||
|
):
|
||||||
|
if self.create_stoploss_order(trade=trade, stop_price=trade.stoploss_or_liquidation):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
logger.warning('All Stoploss orders are cancelled, but unable to recreate one.')
|
||||||
|
|
||||||
|
active_sl_orders = [o for o in stoploss_orders if o not in canceled_sl_orders]
|
||||||
|
if len(active_sl_orders) > 0:
|
||||||
|
last_active_sl_order = active_sl_orders[-1]
|
||||||
|
# Finally we check if stoploss on exchange should be moved up because of trailing.
|
||||||
|
# Triggered Orders are now real orders - so don't replace stoploss anymore
|
||||||
|
if (trade.is_open and
|
||||||
|
last_active_sl_order.get('status_stop') != 'triggered' and
|
||||||
|
(self.config.get('trailing_stop', False) or
|
||||||
|
self.config.get('use_custom_stoploss', False))):
|
||||||
|
# if trailing stoploss is enabled we check if stoploss value has changed
|
||||||
|
# in which case we cancel stoploss order and put another one with new
|
||||||
|
# value immediately
|
||||||
|
self.handle_trailing_stoploss_on_exchange(trade, last_active_sl_order)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
def manage_open_orders(self) -> None:
|
def manage_open_orders(self) -> None:
|
||||||
"""
|
"""
|
||||||
Management of open orders on exchange. Unfilled orders might be cancelled if timeout
|
Management of open orders on exchange. Unfilled orders might be cancelled if timeout
|
||||||
|
|||||||
@@ -185,13 +185,14 @@ class Backtesting:
|
|||||||
# Load detail timeframe if specified
|
# Load detail timeframe if specified
|
||||||
self.timeframe_detail = str(self.config.get('timeframe_detail', ''))
|
self.timeframe_detail = str(self.config.get('timeframe_detail', ''))
|
||||||
if self.timeframe_detail:
|
if self.timeframe_detail:
|
||||||
self.timeframe_detail_min = timeframe_to_minutes(self.timeframe_detail)
|
timeframe_detail_min = timeframe_to_minutes(self.timeframe_detail)
|
||||||
if self.timeframe_min <= self.timeframe_detail_min:
|
self.timeframe_detail_td = timedelta(minutes=timeframe_detail_min)
|
||||||
|
if self.timeframe_min <= timeframe_detail_min:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
"Detail timeframe must be smaller than strategy timeframe.")
|
"Detail timeframe must be smaller than strategy timeframe.")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.timeframe_detail_min = 0
|
self.timeframe_detail_td = timedelta(seconds=0)
|
||||||
self.detail_data: Dict[str, DataFrame] = {}
|
self.detail_data: Dict[str, DataFrame] = {}
|
||||||
self.futures_data: Dict[str, DataFrame] = {}
|
self.futures_data: Dict[str, DataFrame] = {}
|
||||||
|
|
||||||
@@ -1268,7 +1269,7 @@ class Backtesting:
|
|||||||
open_trade_count_start = self.backtest_loop(
|
open_trade_count_start = self.backtest_loop(
|
||||||
det_row, pair, current_time_det, end_date,
|
det_row, pair, current_time_det, end_date,
|
||||||
open_trade_count_start, trade_dir, is_first)
|
open_trade_count_start, trade_dir, is_first)
|
||||||
current_time_det += timedelta(minutes=self.timeframe_detail_min)
|
current_time_det += self.timeframe_detail_td
|
||||||
is_first = False
|
is_first = False
|
||||||
else:
|
else:
|
||||||
self.dataprovider._set_dataframe_max_date(current_time)
|
self.dataprovider._set_dataframe_max_date(current_time)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from sqlalchemy import inspect, select, text, tuple_, update
|
from sqlalchemy import inspect, select, text, update
|
||||||
|
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.persistence.trade_model import Order, Trade
|
from freqtrade.persistence.trade_model import Order, Trade
|
||||||
@@ -91,8 +91,6 @@ def migrate_trades_and_orders_table(
|
|||||||
is_stop_loss_trailing = get_column_def(
|
is_stop_loss_trailing = get_column_def(
|
||||||
cols, 'is_stop_loss_trailing',
|
cols, 'is_stop_loss_trailing',
|
||||||
f'coalesce({stop_loss_pct}, 0.0) <> coalesce({initial_stop_loss_pct}, 0.0)')
|
f'coalesce({stop_loss_pct}, 0.0) <> coalesce({initial_stop_loss_pct}, 0.0)')
|
||||||
stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null')
|
|
||||||
stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null')
|
|
||||||
max_rate = get_column_def(cols, 'max_rate', '0.0')
|
max_rate = get_column_def(cols, 'max_rate', '0.0')
|
||||||
min_rate = get_column_def(cols, 'min_rate', 'null')
|
min_rate = get_column_def(cols, 'min_rate', 'null')
|
||||||
exit_reason = get_column_def(cols, 'sell_reason', get_column_def(cols, 'exit_reason', 'null'))
|
exit_reason = get_column_def(cols, 'sell_reason', get_column_def(cols, 'exit_reason', 'null'))
|
||||||
@@ -160,7 +158,7 @@ def migrate_trades_and_orders_table(
|
|||||||
open_rate_requested, close_rate, close_rate_requested, close_profit,
|
open_rate_requested, close_rate, close_rate_requested, close_profit,
|
||||||
stake_amount, amount, amount_requested, open_date, close_date,
|
stake_amount, amount, amount_requested, open_date, close_date,
|
||||||
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
|
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
|
||||||
is_stop_loss_trailing, stoploss_order_id, stoploss_last_update,
|
is_stop_loss_trailing,
|
||||||
max_rate, min_rate, exit_reason, exit_order_status, strategy, enter_tag,
|
max_rate, min_rate, exit_reason, exit_order_status, strategy, enter_tag,
|
||||||
timeframe, open_trade_value, close_profit_abs,
|
timeframe, open_trade_value, close_profit_abs,
|
||||||
trading_mode, leverage, liquidation_price, is_short,
|
trading_mode, leverage, liquidation_price, is_short,
|
||||||
@@ -180,7 +178,6 @@ def migrate_trades_and_orders_table(
|
|||||||
{initial_stop_loss} initial_stop_loss,
|
{initial_stop_loss} initial_stop_loss,
|
||||||
{initial_stop_loss_pct} initial_stop_loss_pct,
|
{initial_stop_loss_pct} initial_stop_loss_pct,
|
||||||
{is_stop_loss_trailing} is_stop_loss_trailing,
|
{is_stop_loss_trailing} is_stop_loss_trailing,
|
||||||
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
|
|
||||||
{max_rate} max_rate, {min_rate} min_rate,
|
{max_rate} max_rate, {min_rate} min_rate,
|
||||||
case when {exit_reason} = 'sell_signal' then 'exit_signal'
|
case when {exit_reason} = 'sell_signal' then 'exit_signal'
|
||||||
when {exit_reason} = 'custom_sell' then 'custom_exit'
|
when {exit_reason} = 'custom_sell' then 'custom_exit'
|
||||||
@@ -279,6 +276,8 @@ def fix_old_dry_orders(engine):
|
|||||||
with engine.begin() as connection:
|
with engine.begin() as connection:
|
||||||
|
|
||||||
# Update current dry-run Orders where
|
# Update current dry-run Orders where
|
||||||
|
# - stoploss order is Open (will be replaced eventually)
|
||||||
|
# 2nd query:
|
||||||
# - current Order is open
|
# - current Order is open
|
||||||
# - current Trade is closed
|
# - current Trade is closed
|
||||||
# - current Order trade_id not equal to current Trade.id
|
# - current Order trade_id not equal to current Trade.id
|
||||||
@@ -286,11 +285,6 @@ def fix_old_dry_orders(engine):
|
|||||||
|
|
||||||
stmt = update(Order).where(
|
stmt = update(Order).where(
|
||||||
Order.ft_is_open.is_(True),
|
Order.ft_is_open.is_(True),
|
||||||
tuple_(Order.ft_trade_id, Order.order_id).not_in(
|
|
||||||
select(
|
|
||||||
Trade.id, Trade.stoploss_order_id
|
|
||||||
).where(Trade.stoploss_order_id.is_not(None))
|
|
||||||
),
|
|
||||||
Order.ft_order_side == 'stoploss',
|
Order.ft_order_side == 'stoploss',
|
||||||
Order.order_id.like('dry%'),
|
Order.order_id.like('dry%'),
|
||||||
|
|
||||||
|
|||||||
@@ -177,6 +177,8 @@ class Order(ModelBase):
|
|||||||
order_date = safe_value_fallback(order, 'timestamp')
|
order_date = safe_value_fallback(order, 'timestamp')
|
||||||
if order_date:
|
if order_date:
|
||||||
self.order_date = datetime.fromtimestamp(order_date / 1000, tz=timezone.utc)
|
self.order_date = datetime.fromtimestamp(order_date / 1000, tz=timezone.utc)
|
||||||
|
elif not self.order_date:
|
||||||
|
self.order_date = dt_now()
|
||||||
|
|
||||||
self.ft_is_open = True
|
self.ft_is_open = True
|
||||||
if self.status in NON_OPEN_EXCHANGE_STATES:
|
if self.status in NON_OPEN_EXCHANGE_STATES:
|
||||||
@@ -376,10 +378,6 @@ class LocalTrade:
|
|||||||
# percentage value of the initial stop loss
|
# percentage value of the initial stop loss
|
||||||
initial_stop_loss_pct: Optional[float] = None
|
initial_stop_loss_pct: Optional[float] = None
|
||||||
is_stop_loss_trailing: bool = False
|
is_stop_loss_trailing: bool = False
|
||||||
# stoploss order id which is on exchange
|
|
||||||
stoploss_order_id: Optional[str] = None
|
|
||||||
# last update time of the stoploss order on exchange
|
|
||||||
stoploss_last_update: Optional[datetime] = None
|
|
||||||
# absolute value of the highest reached price
|
# absolute value of the highest reached price
|
||||||
max_rate: Optional[float] = None
|
max_rate: Optional[float] = None
|
||||||
# Lowest price reached
|
# Lowest price reached
|
||||||
@@ -469,8 +467,8 @@ class LocalTrade:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def stoploss_last_update_utc(self):
|
def stoploss_last_update_utc(self):
|
||||||
if self.stoploss_last_update:
|
if self.has_open_sl_orders:
|
||||||
return self.stoploss_last_update.replace(tzinfo=timezone.utc)
|
return max(o.order_date_utc for o in self.open_sl_orders)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -526,7 +524,7 @@ class LocalTrade:
|
|||||||
return [o for o in self.orders if o.ft_is_open and o.ft_order_side != 'stoploss']
|
return [o for o in self.orders if o.ft_is_open and o.ft_order_side != 'stoploss']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_open_orders(self) -> int:
|
def has_open_orders(self) -> bool:
|
||||||
"""
|
"""
|
||||||
True if there are open orders for this trade excluding stoploss orders
|
True if there are open orders for this trade excluding stoploss orders
|
||||||
"""
|
"""
|
||||||
@@ -536,6 +534,37 @@ class LocalTrade:
|
|||||||
]
|
]
|
||||||
return len(open_orders_wo_sl) > 0
|
return len(open_orders_wo_sl) > 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def open_sl_orders(self) -> List[Order]:
|
||||||
|
"""
|
||||||
|
All open stoploss orders for this trade
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
o for o in self.orders
|
||||||
|
if o.ft_order_side in ['stoploss'] and o.ft_is_open
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_open_sl_orders(self) -> bool:
|
||||||
|
"""
|
||||||
|
True if there are open stoploss orders for this trade
|
||||||
|
"""
|
||||||
|
open_sl_orders = [
|
||||||
|
o for o in self.orders
|
||||||
|
if o.ft_order_side in ['stoploss'] and o.ft_is_open
|
||||||
|
]
|
||||||
|
return len(open_sl_orders) > 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sl_orders(self) -> List[Order]:
|
||||||
|
"""
|
||||||
|
All stoploss orders for this trade
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
o for o in self.orders
|
||||||
|
if o.ft_order_side in ['stoploss']
|
||||||
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def open_orders_ids(self) -> List[str]:
|
def open_orders_ids(self) -> List[str]:
|
||||||
open_orders_ids_wo_sl = [
|
open_orders_ids_wo_sl = [
|
||||||
@@ -628,11 +657,10 @@ class LocalTrade:
|
|||||||
'stop_loss_abs': self.stop_loss,
|
'stop_loss_abs': self.stop_loss,
|
||||||
'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None,
|
'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None,
|
||||||
'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None,
|
'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None,
|
||||||
'stoploss_order_id': self.stoploss_order_id,
|
'stoploss_last_update': (self.stoploss_last_update_utc.strftime(DATETIME_PRINT_FORMAT)
|
||||||
'stoploss_last_update': (self.stoploss_last_update.strftime(DATETIME_PRINT_FORMAT)
|
if self.stoploss_last_update_utc else None),
|
||||||
if self.stoploss_last_update else None),
|
'stoploss_last_update_timestamp': int(self.stoploss_last_update_utc.timestamp() * 1000
|
||||||
'stoploss_last_update_timestamp': int(self.stoploss_last_update.replace(
|
) if self.stoploss_last_update_utc else None,
|
||||||
tzinfo=timezone.utc).timestamp() * 1000) if self.stoploss_last_update else None,
|
|
||||||
'initial_stop_loss_abs': self.initial_stop_loss,
|
'initial_stop_loss_abs': self.initial_stop_loss,
|
||||||
'initial_stop_loss_ratio': (self.initial_stop_loss_pct
|
'initial_stop_loss_ratio': (self.initial_stop_loss_pct
|
||||||
if self.initial_stop_loss_pct else None),
|
if self.initial_stop_loss_pct else None),
|
||||||
@@ -793,7 +821,6 @@ class LocalTrade:
|
|||||||
logger.info(f'{order.order_type.upper()}_{payment} has been fulfilled for {self}.')
|
logger.info(f'{order.order_type.upper()}_{payment} has been fulfilled for {self}.')
|
||||||
|
|
||||||
elif order.ft_order_side == 'stoploss' and order.status not in ('open', ):
|
elif order.ft_order_side == 'stoploss' and order.status not in ('open', ):
|
||||||
self.stoploss_order_id = None
|
|
||||||
self.close_rate_requested = self.stop_loss
|
self.close_rate_requested = self.stop_loss
|
||||||
self.exit_reason = ExitType.STOPLOSS_ON_EXCHANGE.value
|
self.exit_reason = ExitType.STOPLOSS_ON_EXCHANGE.value
|
||||||
if self.is_open and order.safe_filled > 0:
|
if self.is_open and order.safe_filled > 0:
|
||||||
@@ -1370,11 +1397,6 @@ class LocalTrade:
|
|||||||
exit_order_status=data["exit_order_status"],
|
exit_order_status=data["exit_order_status"],
|
||||||
stop_loss=data["stop_loss_abs"],
|
stop_loss=data["stop_loss_abs"],
|
||||||
stop_loss_pct=data["stop_loss_ratio"],
|
stop_loss_pct=data["stop_loss_ratio"],
|
||||||
stoploss_order_id=data["stoploss_order_id"],
|
|
||||||
stoploss_last_update=(
|
|
||||||
datetime.fromtimestamp(data["stoploss_last_update_timestamp"] // 1000,
|
|
||||||
tz=timezone.utc)
|
|
||||||
if data["stoploss_last_update_timestamp"] else None),
|
|
||||||
initial_stop_loss=data["initial_stop_loss_abs"],
|
initial_stop_loss=data["initial_stop_loss_abs"],
|
||||||
initial_stop_loss_pct=data["initial_stop_loss_ratio"],
|
initial_stop_loss_pct=data["initial_stop_loss_ratio"],
|
||||||
min_rate=data["min_rate"],
|
min_rate=data["min_rate"],
|
||||||
@@ -1481,11 +1503,6 @@ class Trade(ModelBase, LocalTrade):
|
|||||||
Float(), nullable=True) # type: ignore
|
Float(), nullable=True) # type: ignore
|
||||||
is_stop_loss_trailing: Mapped[bool] = mapped_column(
|
is_stop_loss_trailing: Mapped[bool] = mapped_column(
|
||||||
nullable=False, default=False) # type: ignore
|
nullable=False, default=False) # type: ignore
|
||||||
# stoploss order id which is on exchange
|
|
||||||
stoploss_order_id: Mapped[Optional[str]] = mapped_column(
|
|
||||||
String(255), nullable=True, index=True) # type: ignore
|
|
||||||
# last update time of the stoploss order on exchange
|
|
||||||
stoploss_last_update: Mapped[Optional[datetime]] = mapped_column(nullable=True) # type: ignore
|
|
||||||
# absolute value of the highest reached price
|
# absolute value of the highest reached price
|
||||||
max_rate: Mapped[Optional[float]] = mapped_column(
|
max_rate: Mapped[Optional[float]] = mapped_column(
|
||||||
Float(), nullable=True, default=0.0) # type: ignore
|
Float(), nullable=True, default=0.0) # type: ignore
|
||||||
|
|||||||
@@ -315,7 +315,6 @@ class TradeSchema(BaseModel):
|
|||||||
stop_loss_abs: Optional[float] = None
|
stop_loss_abs: Optional[float] = None
|
||||||
stop_loss_ratio: Optional[float] = None
|
stop_loss_ratio: Optional[float] = None
|
||||||
stop_loss_pct: Optional[float] = None
|
stop_loss_pct: Optional[float] = None
|
||||||
stoploss_order_id: Optional[str] = None
|
|
||||||
stoploss_last_update: Optional[str] = None
|
stoploss_last_update: Optional[str] = None
|
||||||
stoploss_last_update_timestamp: Optional[int] = None
|
stoploss_last_update_timestamp: Optional[int] = None
|
||||||
initial_stop_loss_abs: Optional[float] = None
|
initial_stop_loss_abs: Optional[float] = None
|
||||||
|
|||||||
@@ -979,15 +979,16 @@ class RPC:
|
|||||||
except (ExchangeError):
|
except (ExchangeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# cancel stoploss on exchange ...
|
# cancel stoploss on exchange orders ...
|
||||||
if (self._freqtrade.strategy.order_types.get('stoploss_on_exchange')
|
if (self._freqtrade.strategy.order_types.get('stoploss_on_exchange')
|
||||||
and trade.stoploss_order_id):
|
and trade.has_open_sl_orders):
|
||||||
try:
|
|
||||||
self._freqtrade.exchange.cancel_stoploss_order(trade.stoploss_order_id,
|
for oslo in trade.open_sl_orders:
|
||||||
trade.pair)
|
try:
|
||||||
c_count += 1
|
self._freqtrade.exchange.cancel_stoploss_order(oslo.order_id, trade.pair)
|
||||||
except (ExchangeError):
|
c_count += 1
|
||||||
pass
|
except (ExchangeError):
|
||||||
|
pass
|
||||||
|
|
||||||
trade.delete()
|
trade.delete()
|
||||||
self._freqtrade.wallets.update()
|
self._freqtrade.wallets.update()
|
||||||
|
|||||||
@@ -266,7 +266,6 @@ def mock_trade_5(fee, is_short: bool):
|
|||||||
exchange='binance',
|
exchange='binance',
|
||||||
strategy='SampleStrategy',
|
strategy='SampleStrategy',
|
||||||
enter_tag='TEST1',
|
enter_tag='TEST1',
|
||||||
stoploss_order_id=f'prod_stoploss_{direc(is_short)}_3455',
|
|
||||||
timeframe=5,
|
timeframe=5,
|
||||||
is_short=is_short,
|
is_short=is_short,
|
||||||
stop_loss_pct=0.10,
|
stop_loss_pct=0.10,
|
||||||
|
|||||||
@@ -282,7 +282,6 @@ def mock_trade_usdt_5(fee, is_short: bool):
|
|||||||
open_rate=2.0,
|
open_rate=2.0,
|
||||||
exchange='binance',
|
exchange='binance',
|
||||||
strategy='SampleStrategy',
|
strategy='SampleStrategy',
|
||||||
stoploss_order_id=f'prod_stoploss_3455_{direc(is_short)}',
|
|
||||||
timeframe=5,
|
timeframe=5,
|
||||||
is_short=is_short,
|
is_short=is_short,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import ccxt
|
|||||||
import pytest
|
import pytest
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
from freqtrade.enums import CandleType, MarginMode, RunMode, TradingMode
|
||||||
from freqtrade.exceptions import (DDosProtection, DependencyException, ExchangeError,
|
from freqtrade.exceptions import (DDosProtection, DependencyException, ExchangeError,
|
||||||
InsufficientFundsError, InvalidOrderException,
|
InsufficientFundsError, InvalidOrderException,
|
||||||
OperationalException, PricingError, TemporaryError)
|
OperationalException, PricingError, TemporaryError)
|
||||||
@@ -796,7 +796,9 @@ def test_validate_timeframes_failed(default_conf, mocker):
|
|||||||
|
|
||||||
mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock))
|
mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock))
|
||||||
mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={}))
|
mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={}))
|
||||||
mocker.patch(f'{EXMS}.validate_pairs', MagicMock())
|
mocker.patch(f'{EXMS}.validate_pairs')
|
||||||
|
mocker.patch(f'{EXMS}.validate_stakecurrency')
|
||||||
|
mocker.patch(f'{EXMS}.validate_pricing')
|
||||||
with pytest.raises(OperationalException,
|
with pytest.raises(OperationalException,
|
||||||
match=r"Invalid timeframe '3m'. This exchange supports.*"):
|
match=r"Invalid timeframe '3m'. This exchange supports.*"):
|
||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
@@ -806,6 +808,10 @@ def test_validate_timeframes_failed(default_conf, mocker):
|
|||||||
match=r"Timeframes < 1m are currently not supported by Freqtrade."):
|
match=r"Timeframes < 1m are currently not supported by Freqtrade."):
|
||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
# Will not raise an exception in util mode.
|
||||||
|
default_conf['runmode'] = RunMode.UTIL_EXCHANGE
|
||||||
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker):
|
def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker):
|
||||||
default_conf["timeframe"] = "3m"
|
default_conf["timeframe"] = "3m"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -49,7 +49,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
|||||||
stoploss_order_closed['filled'] = stoploss_order_closed['amount']
|
stoploss_order_closed['filled'] = stoploss_order_closed['amount']
|
||||||
|
|
||||||
# Sell first trade based on stoploss, keep 2nd and 3rd trade open
|
# Sell first trade based on stoploss, keep 2nd and 3rd trade open
|
||||||
stop_orders = [stoploss_order_closed, stoploss_order_open, stoploss_order_open]
|
stop_orders = [stoploss_order_closed, stoploss_order_open.copy(), stoploss_order_open.copy()]
|
||||||
stoploss_order_mock = MagicMock(
|
stoploss_order_mock = MagicMock(
|
||||||
side_effect=stop_orders)
|
side_effect=stop_orders)
|
||||||
# Sell 3rd trade (not called for the first trade)
|
# Sell 3rd trade (not called for the first trade)
|
||||||
@@ -100,9 +100,10 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
|||||||
stop_order = stop_orders[idx]
|
stop_order = stop_orders[idx]
|
||||||
stop_order['id'] = f"stop{idx}"
|
stop_order['id'] = f"stop{idx}"
|
||||||
oobj = Order.parse_from_ccxt_object(stop_order, trade.pair, 'stoploss')
|
oobj = Order.parse_from_ccxt_object(stop_order, trade.pair, 'stoploss')
|
||||||
|
oobj.ft_is_open = True
|
||||||
|
|
||||||
trade.orders.append(oobj)
|
trade.orders.append(oobj)
|
||||||
trade.stoploss_order_id = f"stop{idx}"
|
assert len(trade.open_sl_orders) == 1
|
||||||
|
|
||||||
n = freqtrade.exit_positions(trades)
|
n = freqtrade.exit_positions(trades)
|
||||||
assert n == 2
|
assert n == 2
|
||||||
@@ -113,6 +114,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
|||||||
|
|
||||||
# Only order for 3rd trade needs to be cancelled
|
# Only order for 3rd trade needs to be cancelled
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
|
assert stoploss_order_mock.call_count == 3
|
||||||
# Wallets must be updated between stoploss cancellation and selling, and will be updated again
|
# Wallets must be updated between stoploss cancellation and selling, and will be updated again
|
||||||
# during update_trade_state
|
# during update_trade_state
|
||||||
assert wallets_mock.call_count == 4
|
assert wallets_mock.call_count == 4
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -74,7 +74,7 @@ def test_init_dryrun_db(default_conf, tmpdir):
|
|||||||
assert Path(filename).is_file()
|
assert Path(filename).is_file()
|
||||||
|
|
||||||
|
|
||||||
def test_migrate_new(mocker, default_conf, fee, caplog):
|
def test_migrate(mocker, default_conf, fee, caplog):
|
||||||
"""
|
"""
|
||||||
Test Database migration (starting with new pairformat)
|
Test Database migration (starting with new pairformat)
|
||||||
"""
|
"""
|
||||||
@@ -277,8 +277,6 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
|||||||
assert trade.exit_reason is None
|
assert trade.exit_reason is None
|
||||||
assert trade.strategy is None
|
assert trade.strategy is None
|
||||||
assert trade.timeframe == '5m'
|
assert trade.timeframe == '5m'
|
||||||
assert trade.stoploss_order_id == 'dry_stop_order_id222'
|
|
||||||
assert trade.stoploss_last_update is None
|
|
||||||
assert log_has("trying trades_bak1", caplog)
|
assert log_has("trying trades_bak1", caplog)
|
||||||
assert log_has("trying trades_bak2", caplog)
|
assert log_has("trying trades_bak2", caplog)
|
||||||
assert log_has("Running database migration for trades - backup: trades_bak2, orders_bak0",
|
assert log_has("Running database migration for trades - backup: trades_bak2, orders_bak0",
|
||||||
@@ -294,9 +292,10 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
|||||||
assert orders[0].order_id == 'dry_buy_order'
|
assert orders[0].order_id == 'dry_buy_order'
|
||||||
assert orders[0].ft_order_side == 'buy'
|
assert orders[0].ft_order_side == 'buy'
|
||||||
|
|
||||||
|
# All dry-run stoploss orders will be closed
|
||||||
assert orders[-1].order_id == 'dry_stop_order_id222'
|
assert orders[-1].order_id == 'dry_stop_order_id222'
|
||||||
assert orders[-1].ft_order_side == 'stoploss'
|
assert orders[-1].ft_order_side == 'stoploss'
|
||||||
assert orders[-1].ft_is_open is True
|
assert orders[-1].ft_is_open is False
|
||||||
|
|
||||||
assert orders[1].order_id == 'dry_buy_order22'
|
assert orders[1].order_id == 'dry_buy_order22'
|
||||||
assert orders[1].ft_order_side == 'buy'
|
assert orders[1].ft_order_side == 'buy'
|
||||||
|
|||||||
@@ -1432,7 +1432,6 @@ def test_to_json(fee):
|
|||||||
'stop_loss_abs': None,
|
'stop_loss_abs': None,
|
||||||
'stop_loss_ratio': None,
|
'stop_loss_ratio': None,
|
||||||
'stop_loss_pct': None,
|
'stop_loss_pct': None,
|
||||||
'stoploss_order_id': None,
|
|
||||||
'stoploss_last_update': None,
|
'stoploss_last_update': None,
|
||||||
'stoploss_last_update_timestamp': None,
|
'stoploss_last_update_timestamp': None,
|
||||||
'initial_stop_loss_abs': None,
|
'initial_stop_loss_abs': None,
|
||||||
@@ -1500,7 +1499,6 @@ def test_to_json(fee):
|
|||||||
'stop_loss_abs': None,
|
'stop_loss_abs': None,
|
||||||
'stop_loss_pct': None,
|
'stop_loss_pct': None,
|
||||||
'stop_loss_ratio': None,
|
'stop_loss_ratio': None,
|
||||||
'stoploss_order_id': None,
|
|
||||||
'stoploss_last_update': None,
|
'stoploss_last_update': None,
|
||||||
'stoploss_last_update_timestamp': None,
|
'stoploss_last_update_timestamp': None,
|
||||||
'initial_stop_loss_abs': None,
|
'initial_stop_loss_abs': None,
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ def test_trade_fromjson():
|
|||||||
"stop_loss_abs": 0.1981,
|
"stop_loss_abs": 0.1981,
|
||||||
"stop_loss_ratio": -0.216,
|
"stop_loss_ratio": -0.216,
|
||||||
"stop_loss_pct": -21.6,
|
"stop_loss_pct": -21.6,
|
||||||
"stoploss_order_id": null,
|
|
||||||
"stoploss_last_update": "2022-10-18 09:13:42",
|
"stoploss_last_update": "2022-10-18 09:13:42",
|
||||||
"stoploss_last_update_timestamp": 1666077222000,
|
"stoploss_last_update_timestamp": 1666077222000,
|
||||||
"initial_stop_loss_abs": 0.1981,
|
"initial_stop_loss_abs": 0.1981,
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
|||||||
'stop_loss_abs': 9.89e-06,
|
'stop_loss_abs': 9.89e-06,
|
||||||
'stop_loss_pct': -10.0,
|
'stop_loss_pct': -10.0,
|
||||||
'stop_loss_ratio': -0.1,
|
'stop_loss_ratio': -0.1,
|
||||||
'stoploss_order_id': None,
|
|
||||||
'stoploss_last_update': ANY,
|
'stoploss_last_update': ANY,
|
||||||
'stoploss_last_update_timestamp': ANY,
|
'stoploss_last_update_timestamp': ANY,
|
||||||
'initial_stop_loss_abs': 9.89e-06,
|
'initial_stop_loss_abs': 9.89e-06,
|
||||||
@@ -355,7 +354,6 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short):
|
|||||||
rpc._rpc_delete('200')
|
rpc._rpc_delete('200')
|
||||||
|
|
||||||
trades = Trade.session.scalars(select(Trade)).all()
|
trades = Trade.session.scalars(select(Trade)).all()
|
||||||
trades[2].stoploss_order_id = '102'
|
|
||||||
trades[2].orders.append(
|
trades[2].orders.append(
|
||||||
Order(
|
Order(
|
||||||
ft_order_side='stoploss',
|
ft_order_side='stoploss',
|
||||||
|
|||||||
@@ -1174,7 +1174,6 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
|
|||||||
'stop_loss_abs': ANY,
|
'stop_loss_abs': ANY,
|
||||||
'stop_loss_pct': ANY,
|
'stop_loss_pct': ANY,
|
||||||
'stop_loss_ratio': ANY,
|
'stop_loss_ratio': ANY,
|
||||||
'stoploss_order_id': None,
|
|
||||||
'stoploss_last_update': ANY,
|
'stoploss_last_update': ANY,
|
||||||
'stoploss_last_update_timestamp': ANY,
|
'stoploss_last_update_timestamp': ANY,
|
||||||
'initial_stop_loss_abs': 0.0,
|
'initial_stop_loss_abs': 0.0,
|
||||||
@@ -1378,7 +1377,6 @@ def test_api_force_entry(botclient, mocker, fee, endpoint):
|
|||||||
'stop_loss_abs': None,
|
'stop_loss_abs': None,
|
||||||
'stop_loss_pct': None,
|
'stop_loss_pct': None,
|
||||||
'stop_loss_ratio': None,
|
'stop_loss_ratio': None,
|
||||||
'stoploss_order_id': None,
|
|
||||||
'stoploss_last_update': None,
|
'stoploss_last_update': None,
|
||||||
'stoploss_last_update_timestamp': None,
|
'stoploss_last_update_timestamp': None,
|
||||||
'initial_stop_loss_abs': None,
|
'initial_stop_loss_abs': None,
|
||||||
|
|||||||
Reference in New Issue
Block a user