Merge branch 'feat/short' into lev-exchange
This commit is contained in:
commit
2aaf60205e
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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"
|
||||||
|
@ -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")
|
||||||
|
|
||||||
|
@ -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'))
|
||||||
|
@ -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 '
|
||||||
|
@ -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}")
|
||||||
|
@ -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}.')
|
||||||
|
|
||||||
|
@ -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 (
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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',
|
||||||
|
Loading…
Reference in New Issue
Block a user