comment updates, formatting, TODOs

This commit is contained in:
Sam Germain 2021-09-08 01:53:42 -06:00
parent 1379ec7402
commit 695a8fc73b
7 changed files with 58 additions and 39 deletions

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)
@ -98,7 +100,7 @@ class FreqtradeBot(LoggingMixin):
initial_state = self.config.get('initial_state') initial_state = self.config.get('initial_state')
self.state = State[initial_state.upper()] if initial_state else State.STOPPED self.state = State[initial_state.upper()] if initial_state else State.STOPPED
# Protect sell-logic from forcesell and vice versa # Protect exit-logic from forcesell and vice versa
self._sell_lock = Lock() self._sell_lock = Lock()
LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe)) LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe))
@ -170,9 +172,9 @@ 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 exiting is in process, since telegram messages arrive in an different thread.
with self._sell_lock: with self._sell_lock:
trades = Trade.get_open_trades() trades = Trade.get_open_trades()
# First process current opened trades (positions) # First process current opened trades (positions)
@ -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 long buy/short sell 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('*')
@ -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=buy_limit_requested, pair=pair, order_type=order_type, amount=amount, rate=buy_limit_requested,
@ -596,7 +600,7 @@ class FreqtradeBot(LoggingMixin):
def _notify_buy(self, trade: Trade, order_type: str) -> None: def _notify_buy(self, trade: Trade, order_type: str) -> None:
""" """
Sends rpc notification when a buy occurred. Sends rpc notification when a buy/short occurred.
""" """
msg = { msg = {
'trade_id': trade.id, 'trade_id': trade.id,
@ -619,7 +623,7 @@ class FreqtradeBot(LoggingMixin):
def _notify_buy_cancel(self, trade: Trade, order_type: str, reason: str) -> None: def _notify_buy_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
""" """
Sends rpc notification when a buy cancel occurred. Sends rpc notification when a buy/short 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 sell/exit_short orders for open trades (positions)
""" """
trades_closed = 0 trades_closed = 0
for trade in trades: for trade in trades:
@ -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,
@ -744,7 +748,7 @@ class FreqtradeBot(LoggingMixin):
except InvalidOrderException as e: except InvalidOrderException as e:
trade.stoploss_order_id = None 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('Selling the trade forcefully') logger.warning('Exiting the trade forcefully')
self.execute_trade_exit(trade, trade.stop_loss, sell_reason=SellCheckTuple( self.execute_trade_exit(trade, trade.stop_loss, sell_reason=SellCheckTuple(
sell_type=SellType.EMERGENCY_SELL)) sell_type=SellType.EMERGENCY_SELL))
@ -758,6 +762,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)
@ -776,6 +782,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)
@ -791,7 +798,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)
@ -942,6 +949,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'
@ -986,6 +994,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)
@ -994,13 +1004,15 @@ class FreqtradeBot(LoggingMixin):
reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}" reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}"
self.wallets.update() self.wallets.update()
# TODO-lev: Should short and exit_short be an order type?
self._notify_buy_cancel(trade, order_type=self.strategy.order_types['buy'], self._notify_buy_cancel(trade, order_type=self.strategy.order_types['buy'],
reason=reason) reason=reason)
return was_trade_fully_canceled return was_trade_fully_canceled
def handle_cancel_sell(self, trade: Trade, order: Dict, reason: str) -> str: def handle_cancel_sell(self, trade: Trade, order: Dict, reason: str) -> str:
""" """
Sell cancel - cancel order and update trade Sell/exit_short 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
@ -1050,6 +1062,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)
@ -1072,7 +1085,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'
@ -1112,22 +1125,25 @@ class FreqtradeBot(LoggingMixin):
order_type = self.strategy.order_types.get("forcesell", order_type) order_type = self.strategy.order_types.get("forcesell", order_type)
amount = self._safe_sell_amount(trade.pair, trade.amount) amount = self._safe_sell_amount(trade.pair, trade.amount)
time_in_force = self.strategy.order_time_in_force['sell'] time_in_force = self.strategy.order_time_in_force['sell'] # TODO-lev update to exit
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,
time_in_force=time_in_force side="sell",
) amount=amount,
rate=limit,
time_in_force=time_in_force
)
except InsufficientFundsError as e: except InsufficientFundsError as e:
logger.warning(f"Unable to place order {e}.") logger.warning(f"Unable to place order {e}.")
# Try to figure out what went wrong # Try to figure out what went wrong
@ -1138,15 +1154,15 @@ class FreqtradeBot(LoggingMixin):
trade.orders.append(order_obj) trade.orders.append(order_obj)
trade.open_order_id = order['id'] trade.open_order_id = order['id']
trade.sell_order_status = '' trade.sell_order_status = '' # TODO-lev: Update to exit_order_status
trade.close_rate_requested = limit trade.close_rate_requested = limit
trade.sell_reason = sell_reason.sell_reason trade.sell_reason = sell_reason.sell_reason # TODO-lev: Update to exit_reason
# In case of market sell orders the order can be closed immediately # In case of market sell orders the order can be closed immediately
if order.get('status', 'unknown') in ('closed', 'expired'): if order.get('status', 'unknown') in ('closed', 'expired'):
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')
@ -1181,7 +1197,7 @@ class FreqtradeBot(LoggingMixin):
'current_rate': current_rate, 'current_rate': current_rate,
'profit_amount': profit_trade, 'profit_amount': profit_trade,
'profit_ratio': profit_ratio, 'profit_ratio': profit_ratio,
'sell_reason': trade.sell_reason, 'sell_reason': trade.sell_reason, # TODO-lev: change to exit_reason
'open_date': trade.open_date, 'open_date': trade.open_date,
'close_date': trade.close_date or datetime.utcnow(), 'close_date': trade.close_date or datetime.utcnow(),
'stake_currency': self.config['stake_currency'], 'stake_currency': self.config['stake_currency'],
@ -1200,10 +1216,10 @@ class FreqtradeBot(LoggingMixin):
""" """
Sends rpc notification when a sell cancel occurred. Sends rpc notification when a sell cancel occurred.
""" """
if trade.sell_order_status == reason: if trade.sell_order_status == reason: # TODO-lev: Update to exit_order_status
return return
else: else:
trade.sell_order_status = reason trade.sell_order_status = reason # TODO-lev: Update to exit_order_status
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
profit_trade = trade.calc_profit(rate=profit_rate) profit_trade = trade.calc_profit(rate=profit_rate)
@ -1224,7 +1240,7 @@ class FreqtradeBot(LoggingMixin):
'current_rate': current_rate, 'current_rate': current_rate,
'profit_amount': profit_trade, 'profit_amount': profit_trade,
'profit_ratio': profit_ratio, 'profit_ratio': profit_ratio,
'sell_reason': trade.sell_reason, 'sell_reason': trade.sell_reason, # TODO-lev: trade to exit_reason
'open_date': trade.open_date, 'open_date': trade.open_date,
'close_date': trade.close_date, 'close_date': trade.close_date,
'stake_currency': self.config['stake_currency'], 'stake_currency': self.config['stake_currency'],
@ -1310,6 +1326,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:
@ -1386,6 +1403,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

@ -386,7 +386,7 @@ class Backtesting:
detail_data = detail_data.loc[ detail_data = detail_data.loc[
(detail_data['date'] >= sell_candle_time) & (detail_data['date'] >= sell_candle_time) &
(detail_data['date'] < sell_candle_end) (detail_data['date'] < sell_candle_end)
] ]
if len(detail_data) == 0: if len(detail_data) == 0:
# Fall back to "regular" data if no detail data was found for this candle # Fall back to "regular" data if no detail data was found for this candle
return self._get_sell_trade_entry_for_candle(trade, sell_row) return self._get_sell_trade_entry_for_candle(trade, sell_row)

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

@ -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 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

@ -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',