Merge branch 'feat/short' into lev-exchange

This commit is contained in:
Sam Germain 2021-09-12 23:41:34 -06:00
commit 2aaf60205e
14 changed files with 68 additions and 45 deletions

View File

@ -102,3 +102,4 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
HyperoptTools.show_epoch_details(val, total_epochs, print_json, no_header, HyperoptTools.show_epoch_details(val, total_epochs, print_json, no_header,
header_str="Epoch details") header_str="Epoch details")
# TODO-lev: Hyperopt optimal leverage

View File

@ -148,6 +148,7 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
quote_currencies = args.get('quote_currencies', []) quote_currencies = args.get('quote_currencies', [])
try: try:
# TODO-lev: Add leverage amount to get markets that support a certain leverage
pairs = exchange.get_markets(base_currencies=base_currencies, pairs = exchange.get_markets(base_currencies=base_currencies,
quote_currencies=quote_currencies, quote_currencies=quote_currencies,
pairs_only=pairs_only, pairs_only=pairs_only,

View File

@ -3,7 +3,7 @@ from enum import Enum
class SignalType(Enum): class SignalType(Enum):
""" """
Enum to distinguish between buy and sell signals Enum to distinguish between enter and exit signals
""" """
BUY = "buy" BUY = "buy"
SELL = "sell" SELL = "sell"

View File

@ -66,6 +66,7 @@ class FreqtradeBot(LoggingMixin):
init_db(self.config.get('db_url', None), clean_open_orders=self.config['dry_run']) init_db(self.config.get('db_url', None), clean_open_orders=self.config['dry_run'])
# TODO-lev: Do anything with this?
self.wallets = Wallets(self.config, self.exchange) self.wallets = Wallets(self.config, self.exchange)
PairLocks.timeframe = self.config['timeframe'] PairLocks.timeframe = self.config['timeframe']
@ -77,6 +78,7 @@ class FreqtradeBot(LoggingMixin):
# so anything in the Freqtradebot instance should be ready (initialized), including # so anything in the Freqtradebot instance should be ready (initialized), including
# the initial state of the bot. # the initial state of the bot.
# Keep this at the end of this initialization method. # Keep this at the end of this initialization method.
# TODO-lev: Do I need to consider the rpc, pairlists or dataprovider?
self.rpc: RPCManager = RPCManager(self) self.rpc: RPCManager = RPCManager(self)
self.pairlists = PairListManager(self.exchange, self.config) self.pairlists = PairListManager(self.exchange, self.config)
@ -170,7 +172,7 @@ class FreqtradeBot(LoggingMixin):
# Check and handle any timed out open orders # Check and handle any timed out open orders
self.check_handle_timedout() self.check_handle_timedout()
# Protect from collisions with forcesell. # Protect from collisions with forceexit.
# Without this, freqtrade my try to recreate stoploss_on_exchange orders # Without this, freqtrade my try to recreate stoploss_on_exchange orders
# while selling is in process, since telegram messages arrive in an different thread. # while selling is in process, since telegram messages arrive in an different thread.
with self._exit_lock: with self._exit_lock:
@ -289,8 +291,8 @@ class FreqtradeBot(LoggingMixin):
def handle_insufficient_funds(self, trade: Trade): def handle_insufficient_funds(self, trade: Trade):
""" """
Determine if we ever opened a sell order for this trade. Determine if we ever opened a exiting order for this trade.
If not, try update buy fees - otherwise "refind" the open order we obviously lost. If not, try update entering fees - otherwise "refind" the open order we obviously lost.
""" """
sell_order = trade.select_order('sell', None) sell_order = trade.select_order('sell', None)
if sell_order: if sell_order:
@ -312,7 +314,7 @@ class FreqtradeBot(LoggingMixin):
def refind_lost_order(self, trade): def refind_lost_order(self, trade):
""" """
Try refinding a lost trade. Try refinding a lost trade.
Only used when InsufficientFunds appears on sell orders (stoploss or sell). Only used when InsufficientFunds appears on exit orders (stoploss or long sell/short buy).
Tries to walk the stored orders and sell them off eventually. Tries to walk the stored orders and sell them off eventually.
""" """
logger.info(f"Trying to refind lost order for {trade}") logger.info(f"Trying to refind lost order for {trade}")
@ -323,7 +325,7 @@ class FreqtradeBot(LoggingMixin):
logger.debug(f"Order {order} is no longer open.") logger.debug(f"Order {order} is no longer open.")
continue continue
if order.ft_order_side == 'buy': if order.ft_order_side == 'buy':
# Skip buy side - this is handled by reupdate_buy_order_fees # Skip buy side - this is handled by reupdate_enter_order_fees
continue continue
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,
@ -350,7 +352,7 @@ class FreqtradeBot(LoggingMixin):
def enter_positions(self) -> int: def enter_positions(self) -> int:
""" """
Tries to execute buy orders for new trades (positions) Tries to execute entry orders for new trades (positions)
""" """
trades_created = 0 trades_created = 0
@ -366,7 +368,7 @@ class FreqtradeBot(LoggingMixin):
if not whitelist: if not whitelist:
logger.info("No currency pair in active pair whitelist, " logger.info("No currency pair in active pair whitelist, "
"but checking to sell open trades.") "but checking to exit open trades.")
return trades_created return trades_created
if PairLocks.is_global_lock(): if PairLocks.is_global_lock():
lock = PairLocks.get_pair_longest_lock('*') lock = PairLocks.get_pair_longest_lock('*')
@ -385,7 +387,7 @@ class FreqtradeBot(LoggingMixin):
logger.warning('Unable to create trade for %s: %s', pair, exception) logger.warning('Unable to create trade for %s: %s', pair, exception)
if not trades_created: if not trades_created:
logger.debug("Found no buy signals for whitelisted currencies. Trying again...") logger.debug("Found no enter signals for whitelisted currencies. Trying again...")
return trades_created return trades_created
@ -512,7 +514,9 @@ class FreqtradeBot(LoggingMixin):
order_type = self.strategy.order_types['buy'] order_type = self.strategy.order_types['buy']
if forcebuy: if forcebuy:
# Forcebuy can define a different ordertype # Forcebuy can define a different ordertype
# TODO-lev: get a forceshort? What is this
order_type = self.strategy.order_types.get('forcebuy', order_type) order_type = self.strategy.order_types.get('forcebuy', order_type)
# TODO-lev: Will this work for shorting?
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)( if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested, pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
@ -596,7 +600,7 @@ class FreqtradeBot(LoggingMixin):
def _notify_enter(self, trade: Trade, order_type: str) -> None: def _notify_enter(self, trade: Trade, order_type: str) -> None:
""" """
Sends rpc notification when a buy occurred. Sends rpc notification when a entry order occurred.
""" """
msg = { msg = {
'trade_id': trade.id, 'trade_id': trade.id,
@ -619,7 +623,7 @@ class FreqtradeBot(LoggingMixin):
def _notify_enter_cancel(self, trade: Trade, order_type: str, reason: str) -> None: def _notify_enter_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
""" """
Sends rpc notification when a buy cancel occurred. Sends rpc notification when a entry order cancel occurred.
""" """
current_rate = self.exchange.get_rate(trade.pair, refresh=False, side="buy") current_rate = self.exchange.get_rate(trade.pair, refresh=False, side="buy")
@ -665,7 +669,7 @@ class FreqtradeBot(LoggingMixin):
def exit_positions(self, trades: List[Any]) -> int: def exit_positions(self, trades: List[Any]) -> int:
""" """
Tries to execute sell orders for open trades (positions) Tries to execute exit orders for open trades (positions)
""" """
trades_closed = 0 trades_closed = 0
for trade in trades: for trade in trades:
@ -681,7 +685,7 @@ class FreqtradeBot(LoggingMixin):
trades_closed += 1 trades_closed += 1
except DependencyException as exception: except DependencyException as exception:
logger.warning('Unable to sell trade %s: %s', trade.pair, exception) logger.warning('Unable to exit trade %s: %s', trade.pair, exception)
# Updating wallets if any trade occurred # Updating wallets if any trade occurred
if trades_closed: if trades_closed:
@ -691,8 +695,8 @@ class FreqtradeBot(LoggingMixin):
def handle_trade(self, trade: Trade) -> bool: def handle_trade(self, trade: Trade) -> bool:
""" """
Sells the current pair if the threshold is reached and updates the trade record. Sells/exits_short the current pair if the threshold is reached and updates the trade record.
:return: True if trade has been sold, False otherwise :return: True if trade has been sold/exited_short, False otherwise
""" """
if not trade.is_open: if not trade.is_open:
raise DependencyException(f'Attempt to handle closed trade: {trade}') raise DependencyException(f'Attempt to handle closed trade: {trade}')
@ -700,7 +704,7 @@ class FreqtradeBot(LoggingMixin):
logger.debug('Handling %s ...', trade) logger.debug('Handling %s ...', trade)
(buy, sell) = (False, False) (buy, sell) = (False, False)
# TODO-lev: change to use_exit_signal, ignore_roi_if_enter_signal
if (self.config.get('use_sell_signal', True) or if (self.config.get('use_sell_signal', True) or
self.config.get('ignore_roi_if_buy_signal', False)): self.config.get('ignore_roi_if_buy_signal', False)):
analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair, analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair,
@ -762,6 +766,8 @@ class FreqtradeBot(LoggingMixin):
Check if trade is fulfilled in which case the stoploss Check if trade is fulfilled in which case the stoploss
on exchange should be added immediately if stoploss on exchange on exchange should be added immediately if stoploss on exchange
is enabled. is enabled.
# TODO-lev: liquidation price will always be on exchange, even though
# TODO-lev: stoploss_on_exchange might not be enabled
""" """
logger.debug('Handling stoploss on exchange %s ...', trade) logger.debug('Handling stoploss on exchange %s ...', trade)
@ -780,6 +786,7 @@ class FreqtradeBot(LoggingMixin):
# 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'):
# TODO-lev: Update to exit reason
trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order, self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order,
stoploss_order=True) stoploss_order=True)
@ -795,7 +802,7 @@ class FreqtradeBot(LoggingMixin):
# The trade can be closed already (sell-order fill confirmation came in this iteration) # The trade can be closed already (sell-order fill confirmation came in this iteration)
return False return False
# If buy 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
stop_price = trade.open_rate * (1 + stoploss) stop_price = trade.open_rate * (1 + stoploss)
@ -946,6 +953,7 @@ class FreqtradeBot(LoggingMixin):
Buy cancel - cancel order Buy cancel - cancel order
:return: True if order was fully cancelled :return: True if order was fully cancelled
""" """
# TODO-lev: Pay back borrowed/interest and transfer back on leveraged trades
was_trade_fully_canceled = False was_trade_fully_canceled = False
# Cancelled orders may have the status of 'canceled' or 'closed' # Cancelled orders may have the status of 'canceled' or 'closed'
@ -990,6 +998,8 @@ class FreqtradeBot(LoggingMixin):
# to the order dict acquired before cancelling. # to the order dict acquired before cancelling.
# we need to fall back to the values from order if corder does not contain these keys. # we need to fall back to the values from order if corder does not contain these keys.
trade.amount = filled_amount trade.amount = filled_amount
# TODO-lev: Check edge cases, we don't want to make leverage > 1.0 if we don't have to
trade.stake_amount = trade.amount * trade.open_rate trade.stake_amount = trade.amount * trade.open_rate
self.update_trade_state(trade, trade.open_order_id, corder) self.update_trade_state(trade, trade.open_order_id, corder)
@ -1004,7 +1014,7 @@ class FreqtradeBot(LoggingMixin):
def handle_cancel_exit(self, trade: Trade, order: Dict, reason: str) -> str: def handle_cancel_exit(self, trade: Trade, order: Dict, reason: str) -> str:
""" """
Sell cancel - cancel order and update trade exit order cancel - cancel order and update trade
:return: Reason for cancel :return: Reason for cancel
""" """
# if trade is not partially completed, just cancel the order # if trade is not partially completed, just cancel the order
@ -1054,6 +1064,7 @@ class FreqtradeBot(LoggingMixin):
:return: amount to sell :return: amount to sell
:raise: DependencyException: if available balance is not within 2% of the available amount. :raise: DependencyException: if available balance is not within 2% of the available amount.
""" """
# TODO-lev Maybe update?
# Update wallets to ensure amounts tied up in a stoploss is now free! # Update wallets to ensure amounts tied up in a stoploss is now free!
self.wallets.update() self.wallets.update()
trade_base_currency = self.exchange.get_pair_base_currency(pair) trade_base_currency = self.exchange.get_pair_base_currency(pair)
@ -1066,7 +1077,7 @@ class FreqtradeBot(LoggingMixin):
return wallet_amount return wallet_amount
else: else:
raise DependencyException( raise DependencyException(
f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}") f"Not enough amount to exit trade. Trade-amount: {amount}, Wallet: {wallet_amount}")
def execute_trade_exit(self, trade: Trade, limit: float, sell_reason: SellCheckTuple) -> bool: def execute_trade_exit(self, trade: Trade, limit: float, sell_reason: SellCheckTuple) -> bool:
""" """
@ -1076,7 +1087,7 @@ class FreqtradeBot(LoggingMixin):
:param sell_reason: Reason the sell was triggered :param sell_reason: Reason the sell was triggered
:return: True if it succeeds (supported) False (not supported) :return: True if it succeeds (supported) False (not supported)
""" """
sell_type = 'sell' sell_type = 'sell' # TODO-lev: Update to exit
if sell_reason.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): if sell_reason.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
sell_type = 'stoploss' sell_type = 'stoploss'
@ -1121,15 +1132,18 @@ class FreqtradeBot(LoggingMixin):
if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)( if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)(
pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit, pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit,
time_in_force=time_in_force, sell_reason=sell_reason.sell_reason, time_in_force=time_in_force, sell_reason=sell_reason.sell_reason,
current_time=datetime.now(timezone.utc)): current_time=datetime.now(timezone.utc)): # TODO-lev: Update to exit
logger.info(f"User requested abortion of selling {trade.pair}") logger.info(f"User requested abortion of exiting {trade.pair}")
return False return False
try: try:
# Execute sell and update trade record # Execute sell and update trade record
order = self.exchange.create_order(pair=trade.pair, order = self.exchange.create_order(
ordertype=order_type, side="sell", pair=trade.pair,
amount=amount, rate=limit, ordertype=order_type,
side="sell",
amount=amount,
rate=limit,
time_in_force=time_in_force time_in_force=time_in_force
) )
except InsufficientFundsError as e: except InsufficientFundsError as e:
@ -1150,7 +1164,7 @@ class FreqtradeBot(LoggingMixin):
self.update_trade_state(trade, trade.open_order_id, order) self.update_trade_state(trade, trade.open_order_id, order)
Trade.commit() Trade.commit()
# Lock pair for one candle to prevent immediate re-buys # Lock pair for one candle to prevent immediate re-trading
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc), self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
reason='Auto lock') reason='Auto lock')
@ -1314,6 +1328,7 @@ class FreqtradeBot(LoggingMixin):
self.wallets.update() self.wallets.update()
if fee_abs != 0 and self.wallets.get_free(trade_base_currency) >= amount: if fee_abs != 0 and self.wallets.get_free(trade_base_currency) >= amount:
# Eat into dust if we own more than base currency # Eat into dust if we own more than base currency
# TODO-lev: won't be in "base"(quote) currency for shorts
logger.info(f"Fee amount for {trade} was in base currency - " logger.info(f"Fee amount for {trade} was in base currency - "
f"Eating Fee {fee_abs} into dust.") f"Eating Fee {fee_abs} into dust.")
elif fee_abs != 0: elif fee_abs != 0:
@ -1390,6 +1405,7 @@ class FreqtradeBot(LoggingMixin):
trade.update_fee(fee_cost, fee_currency, fee_rate, order.get('side', '')) trade.update_fee(fee_cost, fee_currency, fee_rate, order.get('side', ''))
if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC): if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC):
# TODO-lev: leverage?
logger.warning(f"Amount {amount} does not match amount {trade.amount}") logger.warning(f"Amount {amount} does not match amount {trade.amount}")
raise DependencyException("Half bought? Amounts don't match") raise DependencyException("Half bought? Amounts don't match")

View File

@ -549,7 +549,7 @@ class LocalTrade():
if self.is_open: if self.is_open:
payment = "BUY" if self.is_short else "SELL" payment = "BUY" if self.is_short else "SELL"
# TODO-lev: On shorts, you buy a little bit more than the amount (amount + interest) # TODO-lev: On shorts, you buy a little bit more than the amount (amount + interest)
# This wll only print the original amount # TODO-lev: This wll only print the original amount
logger.info(f'{order_type.upper()}_{payment} has been fulfilled for {self}.') logger.info(f'{order_type.upper()}_{payment} has been fulfilled for {self}.')
# TODO-lev: Double check this # TODO-lev: Double check this
self.close(safe_value_fallback(order, 'average', 'price')) self.close(safe_value_fallback(order, 'average', 'price'))

View File

@ -18,6 +18,7 @@ class PrecisionFilter(IPairList):
pairlist_pos: int) -> None: pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
# TODO-lev: Liquidation price?
if 'stoploss' not in self._config: if 'stoploss' not in self._config:
raise OperationalException( raise OperationalException(
'PrecisionFilter can only work with stoploss defined. Please add the ' 'PrecisionFilter can only work with stoploss defined. Please add the '

View File

@ -127,7 +127,7 @@ class PairListManager():
:return: pairlist - whitelisted pairs :return: pairlist - whitelisted pairs
""" """
try: try:
# TODO-lev: filter for pairlists that are able to trade at the desired leverage
whitelist = expand_pairlist(pairlist, self._exchange.get_markets().keys(), keep_invalid) whitelist = expand_pairlist(pairlist, self._exchange.get_markets().keys(), keep_invalid)
except ValueError as err: except ValueError as err:
logger.error(f"Pair whitelist contains an invalid Wildcard: {err}") logger.error(f"Pair whitelist contains an invalid Wildcard: {err}")

View File

@ -36,6 +36,7 @@ class MaxDrawdown(IProtection):
""" """
LockReason to use LockReason to use
""" """
# TODO-lev: < for shorts?
return (f'{drawdown} > {self._max_allowed_drawdown} in {self.lookback_period_str}, ' return (f'{drawdown} > {self._max_allowed_drawdown} in {self.lookback_period_str}, '
f'locking for {self.stop_duration_str}.') f'locking for {self.stop_duration_str}.')

View File

@ -32,6 +32,7 @@ class StoplossGuard(IProtection):
def _reason(self) -> str: def _reason(self) -> str:
""" """
LockReason to use LockReason to use
#TODO-lev: check if min is the right word for shorts
""" """
return (f'{self._trade_limit} stoplosses in {self._lookback_period} min, ' return (f'{self._trade_limit} stoplosses in {self._lookback_period} min, '
f'locking for {self._stop_duration} min.') f'locking for {self._stop_duration} min.')
@ -51,6 +52,7 @@ class StoplossGuard(IProtection):
# if pair: # if pair:
# filters.append(Trade.pair == pair) # filters.append(Trade.pair == pair)
# trades = Trade.get_trades(filters).all() # trades = Trade.get_trades(filters).all()
# TODO-lev: Liquidation price?
trades1 = Trade.get_trades_proxy(pair=pair, is_open=False, close_date=look_back_until) trades1 = Trade.get_trades_proxy(pair=pair, is_open=False, close_date=look_back_until)
trades = [trade for trade in trades1 if (str(trade.sell_reason) in ( trades = [trade for trade in trades1 if (str(trade.sell_reason) in (

View File

@ -36,6 +36,7 @@ class RPCException(Exception):
raise RPCException('*Status:* `no active trade`') raise RPCException('*Status:* `no active trade`')
""" """
# TODO-lev: Add new configuration options introduced with leveraged/short trading
def __init__(self, message: str) -> None: def __init__(self, message: str) -> None:
super().__init__(self) super().__init__(self)

View File

@ -168,7 +168,7 @@ class IStrategy(ABC, HyperStrategyMixin):
""" """
Check buy enter timeout function callback. Check buy enter timeout function callback.
This method can be used to override the enter-timeout. This method can be used to override the enter-timeout.
It is called whenever a limit buy/short order has been created, It is called whenever a limit entry order has been created,
and is not yet fully filled. and is not yet fully filled.
Configuration options in `unfilledtimeout` will be verified before this, Configuration options in `unfilledtimeout` will be verified before this,
so ensure to set these timeouts high enough. so ensure to set these timeouts high enough.
@ -178,7 +178,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param trade: trade object. :param trade: trade object.
:param order: Order dictionary as returned from CCXT. :param order: Order dictionary as returned from CCXT.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return bool: When True is returned, then the buy/short-order is cancelled. :return bool: When True is returned, then the entry order is cancelled.
""" """
return False return False
@ -212,7 +212,7 @@ class IStrategy(ABC, HyperStrategyMixin):
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, **kwargs) -> bool: time_in_force: str, current_time: datetime, **kwargs) -> bool:
""" """
Called right before placing a buy/short order. Called right before placing a entry order.
Timing for this function is critical, so avoid doing heavy computations or Timing for this function is critical, so avoid doing heavy computations or
network requests in this method. network requests in this method.
@ -236,7 +236,7 @@ class IStrategy(ABC, HyperStrategyMixin):
rate: float, time_in_force: str, sell_reason: str, rate: float, time_in_force: str, sell_reason: str,
current_time: datetime, **kwargs) -> bool: current_time: datetime, **kwargs) -> bool:
""" """
Called right before placing a regular sell/exit_short order. Called right before placing a regular exit order.
Timing for this function is critical, so avoid doing heavy computations or Timing for this function is critical, so avoid doing heavy computations or
network requests in this method. network requests in this method.
@ -410,7 +410,7 @@ class IStrategy(ABC, HyperStrategyMixin):
Checks if a pair is currently locked Checks if a pair is currently locked
The 2nd, optional parameter ensures that locks are applied until the new candle arrives, The 2nd, optional parameter ensures that locks are applied until the new candle arrives,
and not stop at 14:00:00 - while the next candle arrives at 14:00:02 leaving a gap and not stop at 14:00:00 - while the next candle arrives at 14:00:02 leaving a gap
of 2 seconds for a buy/short to happen on an old signal. of 2 seconds for an entry order to happen on an old signal.
:param pair: "Pair to check" :param pair: "Pair to check"
:param candle_date: Date of the last candle. Optional, defaults to current date :param candle_date: Date of the last candle. Optional, defaults to current date
:returns: locking state of the pair in question. :returns: locking state of the pair in question.
@ -426,7 +426,7 @@ class IStrategy(ABC, HyperStrategyMixin):
def analyze_ticker(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def analyze_ticker(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Parses the given candle (OHLCV) data and returns a populated DataFrame Parses the given candle (OHLCV) data and returns a populated DataFrame
add several TA indicators and buy/short signal to it add several TA indicators and entry order signal to it
:param dataframe: Dataframe containing data from exchange :param dataframe: Dataframe containing data from exchange
:param metadata: Metadata dictionary with additional data (e.g. 'pair') :param metadata: Metadata dictionary with additional data (e.g. 'pair')
:return: DataFrame of candle (OHLCV) data with indicator data and signals added :return: DataFrame of candle (OHLCV) data with indicator data and signals added
@ -541,7 +541,7 @@ class IStrategy(ABC, HyperStrategyMixin):
dataframe: DataFrame dataframe: DataFrame
) -> Tuple[bool, bool, Optional[str]]: ) -> Tuple[bool, bool, Optional[str]]:
""" """
Calculates current signal based based on the buy/short or sell/exit_short Calculates current signal based based on the entry order or exit order
columns of the dataframe. columns of the dataframe.
Used by Bot to get the signal to buy, sell, short, or exit_short Used by Bot to get the signal to buy, sell, short, or exit_short
:param pair: pair in format ANT/BTC :param pair: pair in format ANT/BTC
@ -606,7 +606,7 @@ class IStrategy(ABC, HyperStrategyMixin):
sell: bool, low: float = None, high: float = None, sell: bool, low: float = None, high: float = None,
force_stoploss: float = 0) -> SellCheckTuple: force_stoploss: float = 0) -> SellCheckTuple:
""" """
This function evaluates if one of the conditions required to trigger a sell/exit_short This function evaluates if one of the conditions required to trigger an exit order
has been reached, which can either be a stop-loss, ROI or exit-signal. has been reached, which can either be a stop-loss, ROI or exit-signal.
:param low: Only used during backtesting to simulate (long)stoploss/(short)ROI :param low: Only used during backtesting to simulate (long)stoploss/(short)ROI
:param high: Only used during backtesting, to simulate (short)stoploss/(long)ROI :param high: Only used during backtesting, to simulate (short)stoploss/(long)ROI
@ -810,7 +810,7 @@ class IStrategy(ABC, HyperStrategyMixin):
def advise_buy(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def advise_buy(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the buy/short signal for the given dataframe Based on TA indicators, populates the entry order signal for the given dataframe
This method should not be overridden. This method should not be overridden.
:param dataframe: DataFrame :param dataframe: DataFrame
:param metadata: Additional information dictionary, with details like the :param metadata: Additional information dictionary, with details like the
@ -829,7 +829,7 @@ class IStrategy(ABC, HyperStrategyMixin):
def advise_sell(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def advise_sell(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the sell/exit_short signal for the given dataframe Based on TA indicators, populates the exit order signal for the given dataframe
This method should not be overridden. This method should not be overridden.
:param dataframe: DataFrame :param dataframe: DataFrame
:param metadata: Additional information dictionary, with details like the :param metadata: Additional information dictionary, with details like the

View File

@ -1673,7 +1673,7 @@ def test_enter_positions(mocker, default_conf, caplog) -> None:
MagicMock(return_value=False)) MagicMock(return_value=False))
n = freqtrade.enter_positions() n = freqtrade.enter_positions()
assert n == 0 assert n == 0
assert log_has('Found no buy signals for whitelisted currencies. Trying again...', caplog) assert log_has('Found no enter signals for whitelisted currencies. Trying again...', caplog)
# create_trade should be called once for every pair in the whitelist. # create_trade should be called once for every pair in the whitelist.
assert mock_ct.call_count == len(default_conf['exchange']['pair_whitelist']) assert mock_ct.call_count == len(default_conf['exchange']['pair_whitelist'])
@ -1734,7 +1734,7 @@ def test_exit_positions_exception(mocker, default_conf, limit_buy_order, caplog)
) )
n = freqtrade.exit_positions(trades) n = freqtrade.exit_positions(trades)
assert n == 0 assert n == 0
assert log_has('Unable to sell trade ETH/BTC: ', caplog) assert log_has('Unable to exit trade ETH/BTC: ', caplog)
def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> None: def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> None:
@ -3366,7 +3366,7 @@ def test__safe_exit_amount_error(default_conf, fee, caplog, mocker):
) )
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
with pytest.raises(DependencyException, match=r"Not enough amount to sell."): with pytest.raises(DependencyException, match=r"Not enough amount to exit."):
assert freqtrade._safe_exit_amount(trade.pair, trade.amount) assert freqtrade._safe_exit_amount(trade.pair, trade.amount)

View File

@ -90,7 +90,7 @@ def test_enter_exit_side(fee):
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test__set_stop_loss_isolated_liq(fee): def test_set_stop_loss_isolated_liq(fee):
trade = Trade( trade = Trade(
id=2, id=2,
pair='ADA/USDT', pair='ADA/USDT',