Merge pull request #5935 from freqtrade/short_buy_tag_compat

Short buy tag compat
This commit is contained in:
Matthias 2021-11-26 06:29:56 +01:00 committed by GitHub
commit cc9ea1d466
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 153 additions and 105 deletions

View File

@ -43,3 +43,24 @@ As this does however increase risk and provides no benefit, it's been removed fo
Using separate hyperopt files was deprecated in 2021.4 and was removed in 2021.9. Using separate hyperopt files was deprecated in 2021.4 and was removed in 2021.9.
Please switch to the new [Parametrized Strategies](hyperopt.md) to benefit from the new hyperopt interface. Please switch to the new [Parametrized Strategies](hyperopt.md) to benefit from the new hyperopt interface.
## Margin / short changes
// TODO-lev: update version here
## Strategy changes
As strategies now have to support multiple different signal types, some things had to change.
Columns:
* `buy` -> `enter_long`
* `sell` -> `exit_long`
* `buy_tag` -> `enter_tag`
New columns are `enter_short` and `exit_short`, which will initiate short trades (requires additional configuration!)
### webhooks - `buy_tag` has been renamed to `enter_tag`
This should apply only to your strategy and potentially to webhooks.
We will keep a compatibility layer for 1-2 versions (so both `buy_tag` and `enter_tag` will still work), but support for this in webhooks will disappear after that.

View File

@ -77,7 +77,7 @@ class AwesomeStrategy(IStrategy):
*** ***
## Buy Tag ## Enter Tag
When your strategy has multiple buy signals, you can name the signal that triggered. When your strategy has multiple buy signals, you can name the signal that triggered.
Then you can access you buy signal on `custom_sell` Then you can access you buy signal on `custom_sell`
@ -89,7 +89,7 @@ def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
(dataframe['rsi'] < 35) & (dataframe['rsi'] < 35) &
(dataframe['volume'] > 0) (dataframe['volume'] > 0)
), ),
['buy', 'buy_tag']] = (1, 'buy_signal_rsi') ['buy', 'enter_tag']] = (1, 'buy_signal_rsi')
return dataframe return dataframe
@ -97,14 +97,14 @@ def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_r
current_profit: float, **kwargs): current_profit: float, **kwargs):
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze() last_candle = dataframe.iloc[-1].squeeze()
if trade.buy_tag == 'buy_signal_rsi' and last_candle['rsi'] > 80: if trade.enter_tag == 'buy_signal_rsi' and last_candle['rsi'] > 80:
return 'sell_signal_rsi' return 'sell_signal_rsi'
return None return None
``` ```
!!! Note !!! Note
`buy_tag` is limited to 100 characters, remaining data will be truncated. `enter_tag` is limited to 100 characters, remaining data will be truncated.
## Exit tag ## Exit tag

View File

@ -498,7 +498,7 @@ for more information.
& &
(dataframe['volume'] > 0) (dataframe['volume'] > 0)
), ),
['buy', 'buy_tag']] = (1, 'buy_signal_rsi') ['buy', 'enter_tag']] = (1, 'buy_signal_rsi')
return dataframe return dataframe
``` ```

View File

@ -83,7 +83,7 @@ Possible parameters are:
* `fiat_currency` * `fiat_currency`
* `order_type` * `order_type`
* `current_rate` * `current_rate`
* `buy_tag` * `enter_tag`
### Webhookbuycancel ### Webhookbuycancel
@ -101,7 +101,7 @@ Possible parameters are:
* `fiat_currency` * `fiat_currency`
* `order_type` * `order_type`
* `current_rate` * `current_rate`
* `buy_tag` * `enter_tag`
### Webhookbuyfill ### Webhookbuyfill
@ -117,7 +117,7 @@ Possible parameters are:
* `stake_amount` * `stake_amount`
* `stake_currency` * `stake_currency`
* `fiat_currency` * `fiat_currency`
* `buy_tag` * `enter_tag`
### Webhooksell ### Webhooksell

View File

@ -30,7 +30,7 @@ BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
'fee_open', 'fee_close', 'trade_duration', 'fee_open', 'fee_close', 'trade_duration',
'profit_ratio', 'profit_abs', 'sell_reason', 'profit_ratio', 'profit_abs', 'sell_reason',
'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs', 'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs',
'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_tag', 'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'enter_tag',
'is_short' 'is_short'
] ]
# TODO-lev: usage of the above might need compatibility code (buy_tag, is_short?, ...?) # TODO-lev: usage of the above might need compatibility code (buy_tag, is_short?, ...?)

View File

@ -736,8 +736,7 @@ class FreqtradeBot(LoggingMixin):
exchange=self.exchange.id, exchange=self.exchange.id,
open_order_id=order_id, open_order_id=order_id,
strategy=self.strategy.get_strategy_name(), strategy=self.strategy.get_strategy_name(),
# TODO-lev: compatibility layer for buy_tag (!) enter_tag=enter_tag,
buy_tag=enter_tag,
timeframe=timeframe_to_minutes(self.config['timeframe']), timeframe=timeframe_to_minutes(self.config['timeframe']),
leverage=leverage, leverage=leverage,
is_short=is_short, is_short=is_short,
@ -769,7 +768,8 @@ class FreqtradeBot(LoggingMixin):
msg = { msg = {
'trade_id': trade.id, 'trade_id': trade.id,
'type': RPCMessageType.SHORT if trade.is_short else RPCMessageType.BUY, 'type': RPCMessageType.SHORT if trade.is_short else RPCMessageType.BUY,
'buy_tag': trade.buy_tag, 'buy_tag': trade.enter_tag,
'enter_tag': trade.enter_tag,
'exchange': self.exchange.name.capitalize(), 'exchange': self.exchange.name.capitalize(),
'pair': trade.pair, 'pair': trade.pair,
'limit': trade.open_rate, 'limit': trade.open_rate,
@ -794,7 +794,8 @@ class FreqtradeBot(LoggingMixin):
msg = { msg = {
'trade_id': trade.id, 'trade_id': trade.id,
'type': msg_type, 'type': msg_type,
'buy_tag': trade.buy_tag, 'buy_tag': trade.enter_tag,
'enter_tag': trade.enter_tag,
'exchange': self.exchange.name.capitalize(), 'exchange': self.exchange.name.capitalize(),
'pair': trade.pair, 'pair': trade.pair,
'limit': trade.open_rate, 'limit': trade.open_rate,
@ -816,7 +817,8 @@ class FreqtradeBot(LoggingMixin):
msg = { msg = {
'trade_id': trade.id, 'trade_id': trade.id,
'type': msg_type, 'type': msg_type,
'buy_tag': trade.buy_tag, 'buy_tag': trade.enter_tag,
'enter_tag': trade.enter_tag,
'exchange': self.exchange.name.capitalize(), 'exchange': self.exchange.name.capitalize(),
'pair': trade.pair, 'pair': trade.pair,
'open_rate': trade.open_rate, 'open_rate': trade.open_rate,
@ -1399,7 +1401,8 @@ 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,
'buy_tag': trade.buy_tag, 'buy_tag': trade.enter_tag,
'enter_tag': trade.enter_tag,
'sell_reason': trade.sell_reason, 'sell_reason': trade.sell_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(),
@ -1443,7 +1446,8 @@ 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,
'buy_tag': trade.buy_tag, 'buy_tag': trade.enter_tag,
'enter_tag': trade.enter_tag,
'sell_reason': trade.sell_reason, 'sell_reason': trade.sell_reason,
'open_date': trade.open_date, 'open_date': trade.open_date,
'close_date': trade.close_date or datetime.now(timezone.utc), 'close_date': trade.close_date or datetime.now(timezone.utc),

View File

@ -494,7 +494,7 @@ class Backtesting:
fee_open=self.fee, fee_open=self.fee,
fee_close=self.fee, fee_close=self.fee,
is_open=True, is_open=True,
buy_tag=row[ENTER_TAG_IDX] if has_enter_tag else None, enter_tag=row[ENTER_TAG_IDX] if has_enter_tag else None,
exchange=self._exchange_name, exchange=self._exchange_name,
is_short=(direction == 'short'), is_short=(direction == 'short'),
leverage=leverage, leverage=leverage,

View File

@ -422,8 +422,8 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
starting_balance=start_balance, starting_balance=start_balance,
results=results, skip_nan=False) results=results, skip_nan=False)
buy_tag_results = generate_tag_metrics("buy_tag", starting_balance=start_balance, enter_tag_results = generate_tag_metrics("enter_tag", starting_balance=start_balance,
results=results, skip_nan=False) results=results, skip_nan=False)
sell_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades, sell_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades,
results=results) results=results)
@ -448,7 +448,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
'best_pair': best_pair, 'best_pair': best_pair,
'worst_pair': worst_pair, 'worst_pair': worst_pair,
'results_per_pair': pair_results, 'results_per_pair': pair_results,
'results_per_buy_tag': buy_tag_results, 'results_per_enter_tag': enter_tag_results,
'sell_reason_summary': sell_reason_stats, 'sell_reason_summary': sell_reason_stats,
'left_open_trades': left_open_results, 'left_open_trades': left_open_results,
# 'days_breakdown_stats': days_breakdown_stats, # 'days_breakdown_stats': days_breakdown_stats,
@ -634,7 +634,7 @@ def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_curr
:param stake_currency: stake-currency - used to correctly name headers :param stake_currency: stake-currency - used to correctly name headers
:return: pretty printed table with tabulate as string :return: pretty printed table with tabulate as string
""" """
if(tag_type == "buy_tag"): if(tag_type == "enter_tag"):
headers = _get_line_header("TAG", stake_currency) headers = _get_line_header("TAG", stake_currency)
else: else:
headers = _get_line_header_sell("TAG", stake_currency) headers = _get_line_header_sell("TAG", stake_currency)
@ -818,10 +818,12 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '=')) print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '='))
print(table) print(table)
if results.get('results_per_buy_tag') is not None: if (results.get('results_per_enter_tag') is not None
or results.get('results_per_buy_tag') is not None):
# results_per_buy_tag is deprecated and should be removed 2 versions after short golive.
table = text_table_tags( table = text_table_tags(
"buy_tag", "enter_tag",
results['results_per_buy_tag'], results.get('results_per_enter_tag', results.get('results_per_buy_tag')),
stake_currency=stake_currency) stake_currency=stake_currency)
if isinstance(table, str) and len(table) > 0: if isinstance(table, str) and len(table) > 0:

View File

@ -47,7 +47,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
min_rate = get_column_def(cols, 'min_rate', 'null') min_rate = get_column_def(cols, 'min_rate', 'null')
sell_reason = get_column_def(cols, 'sell_reason', 'null') sell_reason = get_column_def(cols, 'sell_reason', 'null')
strategy = get_column_def(cols, 'strategy', 'null') strategy = get_column_def(cols, 'strategy', 'null')
buy_tag = get_column_def(cols, 'buy_tag', 'null') enter_tag = get_column_def(cols, 'buy_tag', get_column_def(cols, 'enter_tag', 'null'))
trading_mode = get_column_def(cols, 'trading_mode', 'null') trading_mode = get_column_def(cols, 'trading_mode', 'null')
@ -98,7 +98,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
stake_amount, amount, amount_requested, open_date, close_date, open_order_id, stake_amount, amount, amount_requested, open_date, close_date, open_order_id,
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct, stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
stoploss_order_id, stoploss_last_update, stoploss_order_id, stoploss_last_update,
max_rate, min_rate, sell_reason, sell_order_status, strategy, buy_tag, max_rate, min_rate, sell_reason, sell_order_status, strategy, enter_tag,
timeframe, open_trade_value, close_profit_abs, timeframe, open_trade_value, close_profit_abs,
trading_mode, leverage, isolated_liq, is_short, trading_mode, leverage, isolated_liq, is_short,
interest_rate, funding_fees interest_rate, funding_fees
@ -116,7 +116,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update, {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
{max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason, {max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason,
{sell_order_status} sell_order_status, {sell_order_status} sell_order_status,
{strategy} strategy, {buy_tag} buy_tag, {timeframe} timeframe, {strategy} strategy, {enter_tag} enter_tag, {timeframe} timeframe,
{open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs, {open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs,
{trading_mode} trading_mode, {leverage} leverage, {isolated_liq} isolated_liq, {trading_mode} trading_mode, {leverage} leverage, {isolated_liq} isolated_liq,
{is_short} is_short, {interest_rate} interest_rate, {is_short} is_short, {interest_rate} interest_rate,
@ -180,7 +180,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
table_back_name = get_backup_name(tabs, 'trades_bak') table_back_name = get_backup_name(tabs, 'trades_bak')
# Check for latest column # Check for latest column
if not has_column(cols, 'funding_fees'): if not has_column(cols, 'enter_tag'):
logger.info(f'Running database migration for trades - backup: {table_back_name}') logger.info(f'Running database migration for trades - backup: {table_back_name}')
migrate_trades_table(decl_base, inspector, engine, table_back_name, cols) migrate_trades_table(decl_base, inspector, engine, table_back_name, cols)
# Reread columns - the above recreated the table! # Reread columns - the above recreated the table!

View File

@ -264,7 +264,7 @@ class LocalTrade():
sell_reason: str = '' sell_reason: str = ''
sell_order_status: str = '' sell_order_status: str = ''
strategy: str = '' strategy: str = ''
buy_tag: Optional[str] = None enter_tag: Optional[str] = None
timeframe: Optional[int] = None timeframe: Optional[int] = None
trading_mode: TradingMode = TradingMode.SPOT trading_mode: TradingMode = TradingMode.SPOT
@ -280,6 +280,14 @@ class LocalTrade():
# Futures properties # Futures properties
funding_fees: Optional[float] = None funding_fees: Optional[float] = None
@property
def buy_tag(self) -> Optional[str]:
"""
Compatibility between buy_tag (old) and enter_tag (new)
Consider buy_tag deprecated
"""
return self.enter_tag
@property @property
def has_no_leverage(self) -> bool: def has_no_leverage(self) -> bool:
"""Returns true if this is a non-leverage, non-short trade""" """Returns true if this is a non-leverage, non-short trade"""
@ -389,7 +397,8 @@ class LocalTrade():
'amount_requested': round(self.amount_requested, 8) if self.amount_requested else None, 'amount_requested': round(self.amount_requested, 8) if self.amount_requested else None,
'stake_amount': round(self.stake_amount, 8), 'stake_amount': round(self.stake_amount, 8),
'strategy': self.strategy, 'strategy': self.strategy,
'buy_tag': self.buy_tag, 'buy_tag': self.enter_tag,
'enter_tag': self.enter_tag,
'timeframe': self.timeframe, 'timeframe': self.timeframe,
'fee_open': self.fee_open, 'fee_open': self.fee_open,
@ -928,7 +937,7 @@ class Trade(_DECL_BASE, LocalTrade):
sell_reason = Column(String(100), nullable=True) sell_reason = Column(String(100), nullable=True)
sell_order_status = Column(String(100), nullable=True) sell_order_status = Column(String(100), nullable=True)
strategy = Column(String(100), nullable=True) strategy = Column(String(100), nullable=True)
buy_tag = Column(String(100), nullable=True) enter_tag = Column(String(100), nullable=True)
timeframe = Column(Integer, nullable=True) timeframe = Column(Integer, nullable=True)
trading_mode = Column(Enum(TradingMode), nullable=True) trading_mode = Column(Enum(TradingMode), nullable=True)
@ -1099,7 +1108,7 @@ class Trade(_DECL_BASE, LocalTrade):
] ]
@staticmethod @staticmethod
def get_buy_tag_performance(pair: Optional[str]) -> List[Dict[str, Any]]: def get_enter_tag_performance(pair: Optional[str]) -> List[Dict[str, Any]]:
""" """
Returns List of dicts containing all Trades, based on buy tag performance Returns List of dicts containing all Trades, based on buy tag performance
Can either be average for all pairs or a specific pair provided Can either be average for all pairs or a specific pair provided
@ -1110,25 +1119,25 @@ class Trade(_DECL_BASE, LocalTrade):
if(pair is not None): if(pair is not None):
filters.append(Trade.pair == pair) filters.append(Trade.pair == pair)
buy_tag_perf = Trade.query.with_entities( enter_tag_perf = Trade.query.with_entities(
Trade.buy_tag, Trade.enter_tag,
func.sum(Trade.close_profit).label('profit_sum'), func.sum(Trade.close_profit).label('profit_sum'),
func.sum(Trade.close_profit_abs).label('profit_sum_abs'), func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
func.count(Trade.pair).label('count') func.count(Trade.pair).label('count')
).filter(*filters)\ ).filter(*filters)\
.group_by(Trade.buy_tag) \ .group_by(Trade.enter_tag) \
.order_by(desc('profit_sum_abs')) \ .order_by(desc('profit_sum_abs')) \
.all() .all()
return [ return [
{ {
'buy_tag': buy_tag if buy_tag is not None else "Other", 'enter_tag': enter_tag if enter_tag is not None else "Other",
'profit_ratio': profit, 'profit_ratio': profit,
'profit_pct': round(profit * 100, 2), 'profit_pct': round(profit * 100, 2),
'profit_abs': profit_abs, 'profit_abs': profit_abs,
'count': count 'count': count
} }
for buy_tag, profit, profit_abs, count in buy_tag_perf for enter_tag, profit, profit_abs, count in enter_tag_perf
] ]
@staticmethod @staticmethod
@ -1178,7 +1187,7 @@ class Trade(_DECL_BASE, LocalTrade):
mix_tag_perf = Trade.query.with_entities( mix_tag_perf = Trade.query.with_entities(
Trade.id, Trade.id,
Trade.buy_tag, Trade.enter_tag,
Trade.sell_reason, Trade.sell_reason,
func.sum(Trade.close_profit).label('profit_sum'), func.sum(Trade.close_profit).label('profit_sum'),
func.sum(Trade.close_profit_abs).label('profit_sum_abs'), func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
@ -1189,12 +1198,12 @@ class Trade(_DECL_BASE, LocalTrade):
.all() .all()
return_list: List[Dict] = [] return_list: List[Dict] = []
for id, buy_tag, sell_reason, profit, profit_abs, count in mix_tag_perf: for id, enter_tag, sell_reason, profit, profit_abs, count in mix_tag_perf:
buy_tag = buy_tag if buy_tag is not None else "Other" enter_tag = enter_tag if enter_tag is not None else "Other"
sell_reason = sell_reason if sell_reason is not None else "Other" sell_reason = sell_reason if sell_reason is not None else "Other"
if(sell_reason is not None and buy_tag is not None): if(sell_reason is not None and enter_tag is not None):
mix_tag = buy_tag + " " + sell_reason mix_tag = enter_tag + " " + sell_reason
i = 0 i = 0
if not any(item["mix_tag"] == mix_tag for item in return_list): if not any(item["mix_tag"] == mix_tag for item in return_list):
return_list.append({'mix_tag': mix_tag, return_list.append({'mix_tag': mix_tag,

View File

@ -185,7 +185,8 @@ class TradeSchema(BaseModel):
amount_requested: float amount_requested: float
stake_amount: float stake_amount: float
strategy: str strategy: str
buy_tag: Optional[str] buy_tag: Optional[str] # Deprecated
enter_tag: Optional[str]
timeframe: int timeframe: int
fee_open: Optional[float] fee_open: Optional[float]
fee_open_cost: Optional[float] fee_open_cost: Optional[float]

View File

@ -782,27 +782,23 @@ class RPC:
return pair_rates return pair_rates
def _rpc_buy_tag_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]: def _rpc_enter_tag_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]:
""" """
Handler for buy tag performance. Handler for buy tag performance.
Shows a performance statistic from finished trades Shows a performance statistic from finished trades
""" """
buy_tags = Trade.get_buy_tag_performance(pair) return Trade.get_enter_tag_performance(pair)
return buy_tags
def _rpc_sell_reason_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]: def _rpc_sell_reason_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]:
""" """
Handler for sell reason performance. Handler for sell reason performance.
Shows a performance statistic from finished trades Shows a performance statistic from finished trades
""" """
sell_reasons = Trade.get_sell_reason_performance(pair) return Trade.get_sell_reason_performance(pair)
return sell_reasons
def _rpc_mix_tag_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]: def _rpc_mix_tag_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]:
""" """
Handler for mix tag (buy_tag + sell_reason) performance. Handler for mix tag (enter_tag + sell_reason) performance.
Shows a performance statistic from finished trades Shows a performance statistic from finished trades
""" """
mix_tags = Trade.get_mix_tag_performance(pair) mix_tags = Trade.get_mix_tag_performance(pair)

View File

@ -154,7 +154,7 @@ class Telegram(RPCHandler):
CommandHandler('trades', self._trades), CommandHandler('trades', self._trades),
CommandHandler('delete', self._delete_trade), CommandHandler('delete', self._delete_trade),
CommandHandler('performance', self._performance), CommandHandler('performance', self._performance),
CommandHandler('buys', self._buy_tag_performance), CommandHandler(['buys', 'entries'], self._enter_tag_performance),
CommandHandler('sells', self._sell_reason_performance), CommandHandler('sells', self._sell_reason_performance),
CommandHandler('mix_tags', self._mix_tag_performance), CommandHandler('mix_tags', self._mix_tag_performance),
CommandHandler('stats', self._stats), CommandHandler('stats', self._stats),
@ -182,7 +182,8 @@ class Telegram(RPCHandler):
CallbackQueryHandler(self._profit, pattern='update_profit'), CallbackQueryHandler(self._profit, pattern='update_profit'),
CallbackQueryHandler(self._balance, pattern='update_balance'), CallbackQueryHandler(self._balance, pattern='update_balance'),
CallbackQueryHandler(self._performance, pattern='update_performance'), CallbackQueryHandler(self._performance, pattern='update_performance'),
CallbackQueryHandler(self._buy_tag_performance, pattern='update_buy_tag_performance'), CallbackQueryHandler(self._enter_tag_performance,
pattern='update_enter_tag_performance'),
CallbackQueryHandler(self._sell_reason_performance, CallbackQueryHandler(self._sell_reason_performance,
pattern='update_sell_reason_performance'), pattern='update_sell_reason_performance'),
CallbackQueryHandler(self._mix_tag_performance, pattern='update_mix_tag_performance'), CallbackQueryHandler(self._mix_tag_performance, pattern='update_mix_tag_performance'),
@ -226,7 +227,7 @@ class Telegram(RPCHandler):
f"{emoji} *{msg['exchange']}:* {'Bought' if is_fill else 'Buying'} {msg['pair']}" f"{emoji} *{msg['exchange']}:* {'Bought' if is_fill else 'Buying'} {msg['pair']}"
f" (#{msg['trade_id']})\n" f" (#{msg['trade_id']})\n"
) )
message += f"*Buy Tag:* `{msg['buy_tag']}`\n" if msg.get('buy_tag', None) else "" message += f"*Enter Tag:* `{msg['enter_tag']}`\n" if msg.get('enter_tag', None) else ""
message += f"*Amount:* `{msg['amount']:.8f}`\n" message += f"*Amount:* `{msg['amount']:.8f}`\n"
if msg['type'] == RPCMessageType.BUY_FILL: if msg['type'] == RPCMessageType.BUY_FILL:
@ -251,7 +252,7 @@ class Telegram(RPCHandler):
microsecond=0) - msg['open_date'].replace(microsecond=0) microsecond=0) - msg['open_date'].replace(microsecond=0)
msg['duration_min'] = msg['duration'].total_seconds() / 60 msg['duration_min'] = msg['duration'].total_seconds() / 60
msg['buy_tag'] = msg['buy_tag'] if "buy_tag" in msg.keys() else None msg['enter_tag'] = msg['enter_tag'] if "enter_tag" in msg.keys() else None
msg['emoji'] = self._get_sell_emoji(msg) msg['emoji'] = self._get_sell_emoji(msg)
# Check if all sell properties are available. # Check if all sell properties are available.
@ -271,7 +272,7 @@ class Telegram(RPCHandler):
f"{'Sold' if is_fill else 'Selling'} {msg['pair']} (#{msg['trade_id']})\n" f"{'Sold' if is_fill else 'Selling'} {msg['pair']} (#{msg['trade_id']})\n"
f"*{'Profit' if is_fill else 'Unrealized Profit'}:* " f"*{'Profit' if is_fill else 'Unrealized Profit'}:* "
f"`{msg['profit_ratio']:.2%}{msg['profit_extra']}`\n" f"`{msg['profit_ratio']:.2%}{msg['profit_extra']}`\n"
f"*Buy Tag:* `{msg['buy_tag']}`\n" f"*Enter Tag:* `{msg['enter_tag']}`\n"
f"*Sell Reason:* `{msg['sell_reason']}`\n" f"*Sell Reason:* `{msg['sell_reason']}`\n"
f"*Duration:* `{msg['duration']} ({msg['duration_min']:.1f} min)`\n" f"*Duration:* `{msg['duration']} ({msg['duration_min']:.1f} min)`\n"
f"*Amount:* `{msg['amount']:.8f}`\n" f"*Amount:* `{msg['amount']:.8f}`\n"
@ -397,7 +398,7 @@ class Telegram(RPCHandler):
"*Trade ID:* `{trade_id}` `(since {open_date_hum})`", "*Trade ID:* `{trade_id}` `(since {open_date_hum})`",
"*Current Pair:* {pair}", "*Current Pair:* {pair}",
"*Amount:* `{amount} ({stake_amount} {base_currency})`", "*Amount:* `{amount} ({stake_amount} {base_currency})`",
"*Buy Tag:* `{buy_tag}`" if r['buy_tag'] else "", "*Enter Tag:* `{enter_tag}`" if r['enter_tag'] else "",
"*Open Rate:* `{open_rate:.8f}`", "*Open Rate:* `{open_rate:.8f}`",
"*Close Rate:* `{close_rate}`" if r['close_rate'] else "", "*Close Rate:* `{close_rate}`" if r['close_rate'] else "",
"*Current Rate:* `{current_rate:.8f}`", "*Current Rate:* `{current_rate:.8f}`",
@ -972,7 +973,7 @@ class Telegram(RPCHandler):
self._send_msg(str(e)) self._send_msg(str(e))
@authorized_only @authorized_only
def _buy_tag_performance(self, update: Update, context: CallbackContext) -> None: def _enter_tag_performance(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /buys PAIR . Handler for /buys PAIR .
Shows a performance statistic from finished trades Shows a performance statistic from finished trades
@ -985,11 +986,11 @@ class Telegram(RPCHandler):
if context.args and isinstance(context.args[0], str): if context.args and isinstance(context.args[0], str):
pair = context.args[0] pair = context.args[0]
trades = self._rpc._rpc_buy_tag_performance(pair) trades = self._rpc._rpc_enter_tag_performance(pair)
output = "<b>Buy Tag Performance:</b>\n" output = "<b>Buy Tag Performance:</b>\n"
for i, trade in enumerate(trades): for i, trade in enumerate(trades):
stat_line = ( stat_line = (
f"{i+1}.\t <code>{trade['buy_tag']}\t" f"{i+1}.\t <code>{trade['enter_tag']}\t"
f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} "
f"({trade['profit_ratio']:.2%}) " f"({trade['profit_ratio']:.2%}) "
f"({trade['count']})</code>\n") f"({trade['count']})</code>\n")
@ -1001,7 +1002,7 @@ class Telegram(RPCHandler):
output += stat_line output += stat_line
self._send_msg(output, parse_mode=ParseMode.HTML, self._send_msg(output, parse_mode=ParseMode.HTML,
reload_able=True, callback_path="update_buy_tag_performance", reload_able=True, callback_path="update_enter_tag_performance",
query=update.callback_query) query=update.callback_query)
except RPCException as e: except RPCException as e:
self._send_msg(str(e)) self._send_msg(str(e))
@ -1277,7 +1278,8 @@ class Telegram(RPCHandler):
" *table :* `will display trades in a table`\n" " *table :* `will display trades in a table`\n"
" `pending buy orders are marked with an asterisk (*)`\n" " `pending buy orders are marked with an asterisk (*)`\n"
" `pending sell orders are marked with a double asterisk (**)`\n" " `pending sell orders are marked with a double asterisk (**)`\n"
"*/buys <pair|none>:* `Shows the buy_tag performance`\n" # TODO-lev: Update commands and help (?)
"*/buys <pair|none>:* `Shows the enter_tag performance`\n"
"*/sells <pair|none>:* `Shows the sell reason performance`\n" "*/sells <pair|none>:* `Shows the sell reason performance`\n"
"*/mix_tags <pair|none>:* `Shows combined buy tag + sell reason performance`\n" "*/mix_tags <pair|none>:* `Shows combined buy tag + sell reason performance`\n"
"*/trades [limit]:* `Lists last closed trades (limited to 10 by default)`\n" "*/trades [limit]:* `Lists last closed trades (limited to 10 by default)`\n"

View File

@ -215,8 +215,6 @@ def patch_get_signal(
) -> None: ) -> None:
""" """
:param mocker: mocker to patch IStrategy class :param mocker: mocker to patch IStrategy class
:param value: which value IStrategy.get_signal() must return
(buy, sell, buy_tag)
:return: None :return: None
""" """
# returns (Signal-direction, signaname) # returns (Signal-direction, signaname)

View File

@ -102,7 +102,7 @@ def mock_trade_2(fee, is_short: bool):
open_order_id=f'dry_run_sell_{direc(is_short)}_12345', open_order_id=f'dry_run_sell_{direc(is_short)}_12345',
strategy='StrategyTestV3', strategy='StrategyTestV3',
timeframe=5, timeframe=5,
buy_tag='TEST1', enter_tag='TEST1',
sell_reason='sell_signal', sell_reason='sell_signal',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2), close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
@ -258,7 +258,7 @@ def mock_trade_5(fee, is_short: bool):
open_rate=0.123, open_rate=0.123,
exchange='binance', exchange='binance',
strategy='SampleStrategy', strategy='SampleStrategy',
buy_tag='TEST1', enter_tag='TEST1',
stoploss_order_id=f'prod_stoploss_{direc(is_short)}_3455', stoploss_order_id=f'prod_stoploss_{direc(is_short)}_3455',
timeframe=5, timeframe=5,
is_short=is_short is_short=is_short
@ -314,7 +314,7 @@ def mock_trade_6(fee, is_short: bool):
open_rate=0.15, open_rate=0.15,
exchange='binance', exchange='binance',
strategy='SampleStrategy', strategy='SampleStrategy',
buy_tag='TEST2', enter_tag='TEST2',
open_order_id=f"prod_sell_{direc(is_short)}_6", open_order_id=f"prod_sell_{direc(is_short)}_6",
timeframe=5, timeframe=5,
is_short=is_short is_short=is_short

View File

@ -621,6 +621,6 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
for c, trade in enumerate(data.trades): for c, trade in enumerate(data.trades):
res = results.iloc[c] res = results.iloc[c]
assert res.sell_reason == trade.sell_reason.value assert res.sell_reason == trade.sell_reason.value
assert res.buy_tag == trade.enter_tag assert res.enter_tag == trade.enter_tag
assert res.open_date == _get_frame_time_from_offset(trade.open_tick) assert res.open_date == _get_frame_time_from_offset(trade.open_tick)
assert res.close_date == _get_frame_time_from_offset(trade.close_tick) assert res.close_date == _get_frame_time_from_offset(trade.close_tick)

View File

@ -698,7 +698,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
'min_rate': [0.10370188, 0.10300000000000001], 'min_rate': [0.10370188, 0.10300000000000001],
'max_rate': [0.10501, 0.1038888], 'max_rate': [0.10501, 0.1038888],
'is_open': [False, False], 'is_open': [False, False],
'buy_tag': [None, None], 'enter_tag': [None, None],
"is_short": [False, False], "is_short": [False, False],
}) })
pd.testing.assert_frame_equal(results, expected) pd.testing.assert_frame_equal(results, expected)

View File

@ -70,6 +70,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'max_rate': ANY, 'max_rate': ANY,
'strategy': ANY, 'strategy': ANY,
'buy_tag': ANY, 'buy_tag': ANY,
'enter_tag': ANY,
'timeframe': 5, 'timeframe': 5,
'open_order_id': ANY, 'open_order_id': ANY,
'close_date': None, 'close_date': None,
@ -143,6 +144,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'max_rate': ANY, 'max_rate': ANY,
'strategy': ANY, 'strategy': ANY,
'buy_tag': ANY, 'buy_tag': ANY,
'enter_tag': ANY,
'timeframe': ANY, 'timeframe': ANY,
'open_order_id': ANY, 'open_order_id': ANY,
'close_date': None, 'close_date': None,
@ -842,8 +844,8 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
assert prec_satoshi(res[0]['profit_pct'], 6.2) assert prec_satoshi(res[0]['profit_pct'], 6.2)
def test_buy_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, def test_enter_tag_performance_handle(default_conf, ticker, limit_buy_order, fee,
limit_sell_order, mocker) -> None: limit_sell_order, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -869,23 +871,23 @@ def test_buy_tag_performance_handle(default_conf, ticker, limit_buy_order, fee,
trade.close_date = datetime.utcnow() trade.close_date = datetime.utcnow()
trade.is_open = False trade.is_open = False
res = rpc._rpc_buy_tag_performance(None) res = rpc._rpc_enter_tag_performance(None)
assert len(res) == 1 assert len(res) == 1
assert res[0]['buy_tag'] == 'Other' assert res[0]['enter_tag'] == 'Other'
assert res[0]['count'] == 1 assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2) assert prec_satoshi(res[0]['profit_pct'], 6.2)
trade.buy_tag = "TEST_TAG" trade.enter_tag = "TEST_TAG"
res = rpc._rpc_buy_tag_performance(None) res = rpc._rpc_enter_tag_performance(None)
assert len(res) == 1 assert len(res) == 1
assert res[0]['buy_tag'] == 'TEST_TAG' assert res[0]['enter_tag'] == 'TEST_TAG'
assert res[0]['count'] == 1 assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2) assert prec_satoshi(res[0]['profit_pct'], 6.2)
def test_buy_tag_performance_handle_2(mocker, default_conf, markets, fee): def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee):
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -896,21 +898,21 @@ def test_buy_tag_performance_handle_2(mocker, default_conf, markets, fee):
create_mock_trades(fee) create_mock_trades(fee)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
res = rpc._rpc_buy_tag_performance(None) res = rpc._rpc_enter_tag_performance(None)
assert len(res) == 2 assert len(res) == 2
assert res[0]['buy_tag'] == 'TEST1' assert res[0]['enter_tag'] == 'TEST1'
assert res[0]['count'] == 1 assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 0.5) assert prec_satoshi(res[0]['profit_pct'], 0.5)
assert res[1]['buy_tag'] == 'Other' assert res[1]['enter_tag'] == 'Other'
assert res[1]['count'] == 1 assert res[1]['count'] == 1
assert prec_satoshi(res[1]['profit_pct'], 1.0) assert prec_satoshi(res[1]['profit_pct'], 1.0)
# Test for a specific pair # Test for a specific pair
res = rpc._rpc_buy_tag_performance('ETC/BTC') res = rpc._rpc_enter_tag_performance('ETC/BTC')
assert len(res) == 1 assert len(res) == 1
assert res[0]['count'] == 1 assert res[0]['count'] == 1
assert res[0]['buy_tag'] == 'TEST1' assert res[0]['enter_tag'] == 'TEST1'
assert prec_satoshi(res[0]['profit_pct'], 0.5) assert prec_satoshi(res[0]['profit_pct'], 0.5)
@ -1020,7 +1022,7 @@ def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee,
assert res[0]['count'] == 1 assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2) assert prec_satoshi(res[0]['profit_pct'], 6.2)
trade.buy_tag = "TESTBUY" trade.enter_tag = "TESTBUY"
trade.sell_reason = "TESTSELL" trade.sell_reason = "TESTSELL"
res = rpc._rpc_mix_tag_performance(None) res = rpc._rpc_mix_tag_performance(None)

View File

@ -959,6 +959,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
'sell_order_status': None, 'sell_order_status': None,
'strategy': CURRENT_TEST_STRATEGY, 'strategy': CURRENT_TEST_STRATEGY,
'buy_tag': None, 'buy_tag': None,
'enter_tag': None,
'timeframe': 5, 'timeframe': 5,
'exchange': 'binance', 'exchange': 'binance',
} }
@ -1117,6 +1118,7 @@ def test_api_forcebuy(botclient, mocker, fee):
'sell_order_status': None, 'sell_order_status': None,
'strategy': CURRENT_TEST_STRATEGY, 'strategy': CURRENT_TEST_STRATEGY,
'buy_tag': None, 'buy_tag': None,
'enter_tag': None,
'timeframe': 5, 'timeframe': 5,
'exchange': 'binance', 'exchange': 'binance',
} }

View File

@ -93,7 +93,7 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], " message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], "
"['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], " "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], "
"['delete'], ['performance'], ['buys'], ['sells'], ['mix_tags'], " "['delete'], ['performance'], ['buys', 'entries'], ['sells'], ['mix_tags'], "
"['stats'], ['daily'], ['weekly'], ['monthly'], " "['stats'], ['daily'], ['weekly'], ['monthly'], "
"['count'], ['locks'], ['unlock', 'delete_locks'], " "['count'], ['locks'], ['unlock', 'delete_locks'], "
"['reload_config', 'reload_conf'], ['show_config', 'show_conf'], " "['reload_config', 'reload_conf'], ['show_config', 'show_conf'], "
@ -189,6 +189,7 @@ def test_telegram_status(default_conf, update, mocker) -> None:
'amount': 90.99181074, 'amount': 90.99181074,
'stake_amount': 90.99181074, 'stake_amount': 90.99181074,
'buy_tag': None, 'buy_tag': None,
'enter_tag': None,
'close_profit_ratio': None, 'close_profit_ratio': None,
'profit': -0.0059, 'profit': -0.0059,
'profit_ratio': -0.0059, 'profit_ratio': -0.0059,
@ -954,6 +955,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
'stake_currency': 'BTC', 'stake_currency': 'BTC',
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'buy_tag': ANY, 'buy_tag': ANY,
'enter_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value, 'sell_reason': SellType.FORCE_SELL.value,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
@ -1018,6 +1020,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
'stake_currency': 'BTC', 'stake_currency': 'BTC',
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'buy_tag': ANY, 'buy_tag': ANY,
'enter_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value, 'sell_reason': SellType.FORCE_SELL.value,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
@ -1072,6 +1075,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
'stake_currency': 'BTC', 'stake_currency': 'BTC',
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'buy_tag': ANY, 'buy_tag': ANY,
'enter_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value, 'sell_reason': SellType.FORCE_SELL.value,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
@ -1235,14 +1239,14 @@ def test_buy_tag_performance_handle(default_conf, update, ticker, fee,
# Simulate fulfilled LIMIT_BUY order for trade # Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_buy_order) trade.update(limit_buy_order)
trade.buy_tag = "TESTBUY" trade.enter_tag = "TESTBUY"
# Simulate fulfilled LIMIT_SELL order for trade # Simulate fulfilled LIMIT_SELL order for trade
trade.update(limit_sell_order) trade.update(limit_sell_order)
trade.close_date = datetime.utcnow() trade.close_date = datetime.utcnow()
trade.is_open = False trade.is_open = False
telegram._buy_tag_performance(update=update, context=MagicMock()) telegram._enter_tag_performance(update=update, context=MagicMock())
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert 'Buy Tag Performance' in msg_mock.call_args_list[0][0][0] assert 'Buy Tag Performance' in msg_mock.call_args_list[0][0][0]
assert '<code>TESTBUY\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0] assert '<code>TESTBUY\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0]
@ -1297,7 +1301,7 @@ def test_mix_tag_performance_handle(default_conf, update, ticker, fee,
# Simulate fulfilled LIMIT_BUY order for trade # Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_buy_order) trade.update(limit_buy_order)
trade.buy_tag = "TESTBUY" trade.enter_tag = "TESTBUY"
trade.sell_reason = "TESTSELL" trade.sell_reason = "TESTSELL"
# Simulate fulfilled LIMIT_SELL order for trade # Simulate fulfilled LIMIT_SELL order for trade
@ -1598,7 +1602,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
msg = { msg = {
'type': RPCMessageType.BUY, 'type': RPCMessageType.BUY,
'trade_id': 1, 'trade_id': 1,
'buy_tag': 'buy_signal_01', 'enter_tag': 'buy_signal_01',
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'limit': 1.099e-05, 'limit': 1.099e-05,
@ -1616,7 +1620,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
telegram.send_msg(msg) telegram.send_msg(msg)
assert msg_mock.call_args[0][0] \ assert msg_mock.call_args[0][0] \
== '\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n' \ == '\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n' \
'*Buy Tag:* `buy_signal_01`\n' \ '*Enter Tag:* `buy_signal_01`\n' \
'*Amount:* `1333.33333333`\n' \ '*Amount:* `1333.33333333`\n' \
'*Open Rate:* `0.00001099`\n' \ '*Open Rate:* `0.00001099`\n' \
'*Current Rate:* `0.00001099`\n' \ '*Current Rate:* `0.00001099`\n' \
@ -1644,7 +1648,7 @@ def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None:
telegram.send_msg({ telegram.send_msg({
'type': RPCMessageType.BUY_CANCEL, 'type': RPCMessageType.BUY_CANCEL,
'buy_tag': 'buy_signal_01', 'enter_tag': 'buy_signal_01',
'trade_id': 1, 'trade_id': 1,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
@ -1691,7 +1695,7 @@ def test_send_msg_buy_fill_notification(default_conf, mocker) -> None:
telegram.send_msg({ telegram.send_msg({
'type': RPCMessageType.BUY_FILL, 'type': RPCMessageType.BUY_FILL,
'trade_id': 1, 'trade_id': 1,
'buy_tag': 'buy_signal_01', 'enter_tag': 'buy_signal_01',
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'stake_amount': 0.001, 'stake_amount': 0.001,
@ -1705,7 +1709,7 @@ def test_send_msg_buy_fill_notification(default_conf, mocker) -> None:
assert msg_mock.call_args[0][0] \ assert msg_mock.call_args[0][0] \
== '\N{CHECK MARK} *Binance:* Bought ETH/BTC (#1)\n' \ == '\N{CHECK MARK} *Binance:* Bought ETH/BTC (#1)\n' \
'*Buy Tag:* `buy_signal_01`\n' \ '*Enter Tag:* `buy_signal_01`\n' \
'*Amount:* `1333.33333333`\n' \ '*Amount:* `1333.33333333`\n' \
'*Open Rate:* `0.00001099`\n' \ '*Open Rate:* `0.00001099`\n' \
'*Total:* `(0.00100000 BTC, 12.345 USD)`' '*Total:* `(0.00100000 BTC, 12.345 USD)`'
@ -1732,7 +1736,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'profit_ratio': -0.57405275, 'profit_ratio': -0.57405275,
'stake_currency': 'ETH', 'stake_currency': 'ETH',
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'buy_tag': 'buy_signal1', 'enter_tag': 'buy_signal1',
'sell_reason': SellType.STOP_LOSS.value, 'sell_reason': SellType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(hours=-1), 'open_date': arrow.utcnow().shift(hours=-1),
'close_date': arrow.utcnow(), 'close_date': arrow.utcnow(),
@ -1740,7 +1744,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
assert msg_mock.call_args[0][0] \ assert msg_mock.call_args[0][0] \
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n' == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n' '*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
'*Buy Tag:* `buy_signal1`\n' '*Enter Tag:* `buy_signal1`\n'
'*Sell Reason:* `stop_loss`\n' '*Sell Reason:* `stop_loss`\n'
'*Duration:* `1:00:00 (60.0 min)`\n' '*Duration:* `1:00:00 (60.0 min)`\n'
'*Amount:* `1333.33333333`\n' '*Amount:* `1333.33333333`\n'
@ -1764,7 +1768,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'profit_amount': -0.05746268, 'profit_amount': -0.05746268,
'profit_ratio': -0.57405275, 'profit_ratio': -0.57405275,
'stake_currency': 'ETH', 'stake_currency': 'ETH',
'buy_tag': 'buy_signal1', 'enter_tag': 'buy_signal1',
'sell_reason': SellType.STOP_LOSS.value, 'sell_reason': SellType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30), 'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
'close_date': arrow.utcnow(), 'close_date': arrow.utcnow(),
@ -1772,7 +1776,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
assert msg_mock.call_args[0][0] \ assert msg_mock.call_args[0][0] \
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n' == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
'*Unrealized Profit:* `-57.41%`\n' '*Unrealized Profit:* `-57.41%`\n'
'*Buy Tag:* `buy_signal1`\n' '*Enter Tag:* `buy_signal1`\n'
'*Sell Reason:* `stop_loss`\n' '*Sell Reason:* `stop_loss`\n'
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n' '*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
'*Amount:* `1333.33333333`\n' '*Amount:* `1333.33333333`\n'
@ -1835,7 +1839,7 @@ def test_send_msg_sell_fill_notification(default_conf, mocker) -> None:
'profit_amount': -0.05746268, 'profit_amount': -0.05746268,
'profit_ratio': -0.57405275, 'profit_ratio': -0.57405275,
'stake_currency': 'ETH', 'stake_currency': 'ETH',
'buy_tag': 'buy_signal1', 'enter_tag': 'buy_signal1',
'sell_reason': SellType.STOP_LOSS.value, 'sell_reason': SellType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30), 'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
'close_date': arrow.utcnow(), 'close_date': arrow.utcnow(),
@ -1843,7 +1847,7 @@ def test_send_msg_sell_fill_notification(default_conf, mocker) -> None:
assert msg_mock.call_args[0][0] \ assert msg_mock.call_args[0][0] \
== ('\N{WARNING SIGN} *Binance:* Sold KEY/ETH (#1)\n' == ('\N{WARNING SIGN} *Binance:* Sold KEY/ETH (#1)\n'
'*Profit:* `-57.41%`\n' '*Profit:* `-57.41%`\n'
'*Buy Tag:* `buy_signal1`\n' '*Enter Tag:* `buy_signal1`\n'
'*Sell Reason:* `stop_loss`\n' '*Sell Reason:* `stop_loss`\n'
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n' '*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
'*Amount:* `1333.33333333`\n' '*Amount:* `1333.33333333`\n'
@ -1894,7 +1898,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
telegram.send_msg({ telegram.send_msg({
'type': RPCMessageType.BUY, 'type': RPCMessageType.BUY,
'buy_tag': 'buy_signal_01', 'enter_tag': 'buy_signal_01',
'trade_id': 1, 'trade_id': 1,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
@ -1909,7 +1913,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
'open_date': arrow.utcnow().shift(hours=-1) 'open_date': arrow.utcnow().shift(hours=-1)
}) })
assert msg_mock.call_args[0][0] == ('\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n' assert msg_mock.call_args[0][0] == ('\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n'
'*Buy Tag:* `buy_signal_01`\n' '*Enter Tag:* `buy_signal_01`\n'
'*Amount:* `1333.33333333`\n' '*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00001099`\n' '*Open Rate:* `0.00001099`\n'
'*Current Rate:* `0.00001099`\n' '*Current Rate:* `0.00001099`\n'
@ -1935,14 +1939,14 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
'profit_ratio': -0.57405275, 'profit_ratio': -0.57405275,
'stake_currency': 'ETH', 'stake_currency': 'ETH',
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'buy_tag': 'buy_signal1', 'enter_tag': 'buy_signal1',
'sell_reason': SellType.STOP_LOSS.value, 'sell_reason': SellType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(hours=-2, minutes=-35, seconds=-3), 'open_date': arrow.utcnow().shift(hours=-2, minutes=-35, seconds=-3),
'close_date': arrow.utcnow(), 'close_date': arrow.utcnow(),
}) })
assert msg_mock.call_args[0][0] == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n' assert msg_mock.call_args[0][0] == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
'*Unrealized Profit:* `-57.41%`\n' '*Unrealized Profit:* `-57.41%`\n'
'*Buy Tag:* `buy_signal1`\n' '*Enter Tag:* `buy_signal1`\n'
'*Sell Reason:* `stop_loss`\n' '*Sell Reason:* `stop_loss`\n'
'*Duration:* `2:35:03 (155.1 min)`\n' '*Duration:* `2:35:03 (155.1 min)`\n'
'*Amount:* `1333.33333333`\n' '*Amount:* `1333.33333333`\n'

View File

@ -2869,6 +2869,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
'amount': amt, 'amount': amt,
'order_type': 'limit', 'order_type': 'limit',
'buy_tag': None, 'buy_tag': None,
'enter_tag': None,
'open_rate': open_rate, 'open_rate': open_rate,
'current_rate': 2.01 if is_short else 2.3, 'current_rate': 2.01 if is_short else 2.3,
'profit_amount': 0.29554455 if is_short else 5.685, 'profit_amount': 0.29554455 if is_short else 5.685,
@ -2925,6 +2926,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
'amount': 29.70297029 if is_short else 30.0, 'amount': 29.70297029 if is_short else 30.0,
'order_type': 'limit', 'order_type': 'limit',
'buy_tag': None, 'buy_tag': None,
'enter_tag': None,
'open_rate': 2.02 if is_short else 2.0, 'open_rate': 2.02 if is_short else 2.0,
'current_rate': 2.2 if is_short else 2.0, 'current_rate': 2.2 if is_short else 2.0,
'profit_amount': -5.65990099 if is_short else -0.00075, 'profit_amount': -5.65990099 if is_short else -0.00075,
@ -3002,6 +3004,7 @@ def test_execute_trade_exit_custom_exit_price(
'amount': amount, 'amount': amount,
'order_type': 'limit', 'order_type': 'limit',
'buy_tag': None, 'buy_tag': None,
'enter_tag': None,
'open_rate': open_rate, 'open_rate': open_rate,
'current_rate': current_rate, 'current_rate': current_rate,
'profit_amount': profit_amount, 'profit_amount': profit_amount,
@ -3066,6 +3069,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
'amount': 29.70297029 if is_short else 30.0, 'amount': 29.70297029 if is_short else 30.0,
'order_type': 'limit', 'order_type': 'limit',
'buy_tag': None, 'buy_tag': None,
'enter_tag': None,
'open_rate': 2.02 if is_short else 2.0, 'open_rate': 2.02 if is_short else 2.0,
'current_rate': 2.2 if is_short else 2.0, 'current_rate': 2.2 if is_short else 2.0,
'profit_amount': -0.3 if is_short else -0.8985, 'profit_amount': -0.3 if is_short else -0.8985,
@ -3319,6 +3323,7 @@ def test_execute_trade_exit_market_order(
'amount': round(amount, 9), 'amount': round(amount, 9),
'order_type': 'market', 'order_type': 'market',
'buy_tag': None, 'buy_tag': None,
'enter_tag': None,
'open_rate': open_rate, 'open_rate': open_rate,
'current_rate': current_rate, 'current_rate': current_rate,
'profit_amount': profit_amount, 'profit_amount': profit_amount,

View File

@ -1551,7 +1551,7 @@ def test_to_json(default_conf, fee):
open_date=arrow.utcnow().shift(hours=-2).datetime, open_date=arrow.utcnow().shift(hours=-2).datetime,
open_rate=0.123, open_rate=0.123,
exchange='binance', exchange='binance',
buy_tag=None, enter_tag=None,
open_order_id='dry_run_buy_12345' open_order_id='dry_run_buy_12345'
) )
result = trade.to_json() result = trade.to_json()
@ -1602,6 +1602,7 @@ def test_to_json(default_conf, fee):
'max_rate': None, 'max_rate': None,
'strategy': None, 'strategy': None,
'buy_tag': None, 'buy_tag': None,
'enter_tag': None,
'timeframe': None, 'timeframe': None,
'exchange': 'binance', 'exchange': 'binance',
'leverage': None, 'leverage': None,
@ -1624,7 +1625,7 @@ def test_to_json(default_conf, fee):
close_date=arrow.utcnow().shift(hours=-1).datetime, close_date=arrow.utcnow().shift(hours=-1).datetime,
open_rate=0.123, open_rate=0.123,
close_rate=0.125, close_rate=0.125,
buy_tag='buys_signal_001', enter_tag='buys_signal_001',
exchange='binance', exchange='binance',
) )
result = trade.to_json() result = trade.to_json()
@ -1675,6 +1676,7 @@ def test_to_json(default_conf, fee):
'sell_order_status': None, 'sell_order_status': None,
'strategy': None, 'strategy': None,
'buy_tag': 'buys_signal_001', 'buy_tag': 'buys_signal_001',
'enter_tag': 'buys_signal_001',
'timeframe': None, 'timeframe': None,
'exchange': 'binance', 'exchange': 'binance',
'leverage': None, 'leverage': None,
@ -2116,7 +2118,7 @@ def test_Trade_object_idem():
'get_open_order_trades', 'get_open_order_trades',
'get_trades', 'get_trades',
'get_sell_reason_performance', 'get_sell_reason_performance',
'get_buy_tag_performance', 'get_enter_tag_performance',
'get_mix_tag_performance', 'get_mix_tag_performance',
) )