Fixed flake 8, changed sell_tag to exit_tag and fixed telegram functions

This commit is contained in:
theluxaz 2021-10-18 23:56:41 +03:00
parent 0bb7ea10ab
commit 69a59cdf37
10 changed files with 206 additions and 221 deletions

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', 'sell_tag'] 'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_tag', 'exit_tag']
def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str: def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str:

View File

@ -14,4 +14,4 @@ class SignalTagType(Enum):
Enum for signal columns Enum for signal columns
""" """
BUY_TAG = "buy_tag" BUY_TAG = "buy_tag"
SELL_TAG = "sell_tag" EXIT_TAG = "exit_tag"

View File

@ -420,7 +420,7 @@ class FreqtradeBot(LoggingMixin):
return False return False
# running get_signal on historical data fetched # running get_signal on historical data fetched
(buy, sell, buy_tag,sell_tag) = self.strategy.get_signal( (buy, sell, buy_tag, exit_tag) = self.strategy.get_signal(
pair, pair,
self.strategy.timeframe, self.strategy.timeframe,
analyzed_df analyzed_df
@ -700,15 +700,14 @@ class FreqtradeBot(LoggingMixin):
logger.debug('Handling %s ...', trade) logger.debug('Handling %s ...', trade)
(buy, sell) = (False, False) (buy, sell) = (False, False)
exit_tag = None
sell_tag=None
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,
self.strategy.timeframe) self.strategy.timeframe)
(buy, sell, buy_tag, sell_tag) = self.strategy.get_signal( (buy, sell, buy_tag, exit_tag) = self.strategy.get_signal(
trade.pair, trade.pair,
self.strategy.timeframe, self.strategy.timeframe,
analyzed_df analyzed_df
@ -716,7 +715,7 @@ class FreqtradeBot(LoggingMixin):
logger.debug('checking sell') logger.debug('checking sell')
sell_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell") sell_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell")
if self._check_and_execute_exit(trade, sell_rate, buy, sell, sell_tag): if self._check_and_execute_exit(trade, sell_rate, buy, sell, exit_tag):
return True return True
logger.debug('Found no sell signal for %s.', trade) logger.debug('Found no sell signal for %s.', trade)
@ -854,7 +853,7 @@ class FreqtradeBot(LoggingMixin):
f"for pair {trade.pair}.") f"for pair {trade.pair}.")
def _check_and_execute_exit(self, trade: Trade, exit_rate: float, def _check_and_execute_exit(self, trade: Trade, exit_rate: float,
buy: bool, sell: bool, sell_tag: Optional[str]) -> bool: buy: bool, sell: bool, exit_tag: Optional[str]) -> bool:
""" """
Check and execute exit Check and execute exit
""" """
@ -865,8 +864,9 @@ class FreqtradeBot(LoggingMixin):
) )
if should_sell.sell_flag: if should_sell.sell_flag:
logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}. Tag: {sell_tag if sell_tag is not None else "None"}') logger.info(
self.execute_trade_exit(trade, exit_rate, should_sell,sell_tag) f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}. Tag: {exit_tag if exit_tag is not None else "None"}')
self.execute_trade_exit(trade, exit_rate, should_sell, exit_tag)
return True return True
return False return False
@ -1067,7 +1067,12 @@ class FreqtradeBot(LoggingMixin):
raise DependencyException( raise DependencyException(
f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}") f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}")
def execute_trade_exit(self, trade: Trade, limit: float, sell_reason: SellCheckTuple, sell_tag: Optional[str] = None) -> bool: def execute_trade_exit(
self,
trade: Trade,
limit: float,
sell_reason: SellCheckTuple,
exit_tag: Optional[str] = None) -> bool:
""" """
Executes a trade exit for the given trade and limit Executes a trade exit for the given trade and limit
:param trade: Trade instance :param trade: Trade instance
@ -1144,8 +1149,8 @@ class FreqtradeBot(LoggingMixin):
trade.sell_order_status = '' trade.sell_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
if(sell_tag is not None): if(exit_tag is not None):
trade.sell_tag = sell_tag trade.exit_tag = exit_tag
# 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)
@ -1187,7 +1192,7 @@ class FreqtradeBot(LoggingMixin):
'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,
'sell_tag': trade.sell_tag, 'exit_tag': trade.exit_tag,
'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'],
@ -1231,7 +1236,7 @@ class FreqtradeBot(LoggingMixin):
'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,
'sell_tag': trade.sell_tag, 'exit_tag': trade.exit_tag,
'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),
'stake_currency': self.config['stake_currency'], 'stake_currency': self.config['stake_currency'],

View File

@ -44,7 +44,8 @@ SELL_IDX = 4
LOW_IDX = 5 LOW_IDX = 5
HIGH_IDX = 6 HIGH_IDX = 6
BUY_TAG_IDX = 7 BUY_TAG_IDX = 7
SELL_TAG_IDX = 8 EXIT_TAG_IDX = 8
class Backtesting: class Backtesting:
""" """
@ -247,7 +248,7 @@ class Backtesting:
""" """
# Every change to this headers list must evaluate further usages of the resulting tuple # Every change to this headers list must evaluate further usages of the resulting tuple
# and eventually change the constants for indexes at the top # and eventually change the constants for indexes at the top
headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high', 'buy_tag', 'sell_tag'] headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high', 'buy_tag', 'exit_tag']
data: Dict = {} data: Dict = {}
self.progress.init_step(BacktestState.CONVERT, len(processed)) self.progress.init_step(BacktestState.CONVERT, len(processed))
@ -259,7 +260,7 @@ class Backtesting:
pair_data.loc[:, 'buy'] = 0 # cleanup if buy_signal is exist pair_data.loc[:, 'buy'] = 0 # cleanup if buy_signal is exist
pair_data.loc[:, 'sell'] = 0 # cleanup if sell_signal is exist pair_data.loc[:, 'sell'] = 0 # cleanup if sell_signal is exist
pair_data.loc[:, 'buy_tag'] = None # cleanup if buy_tag is exist pair_data.loc[:, 'buy_tag'] = None # cleanup if buy_tag is exist
pair_data.loc[:, 'sell_tag'] = None # cleanup if sell_tag is exist pair_data.loc[:, 'exit_tag'] = None # cleanup if exit_tag is exist
df_analyzed = self.strategy.advise_sell( df_analyzed = self.strategy.advise_sell(
self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair}).copy() self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair}).copy()
@ -271,7 +272,7 @@ class Backtesting:
df_analyzed.loc[:, 'buy'] = df_analyzed.loc[:, 'buy'].shift(1) df_analyzed.loc[:, 'buy'] = df_analyzed.loc[:, 'buy'].shift(1)
df_analyzed.loc[:, 'sell'] = df_analyzed.loc[:, 'sell'].shift(1) df_analyzed.loc[:, 'sell'] = df_analyzed.loc[:, 'sell'].shift(1)
df_analyzed.loc[:, 'buy_tag'] = df_analyzed.loc[:, 'buy_tag'].shift(1) df_analyzed.loc[:, 'buy_tag'] = df_analyzed.loc[:, 'buy_tag'].shift(1)
df_analyzed.loc[:, 'sell_tag'] = df_analyzed.loc[:, 'sell_tag'].shift(1) df_analyzed.loc[:, 'exit_tag'] = df_analyzed.loc[:, 'exit_tag'].shift(1)
# Update dataprovider cache # Update dataprovider cache
self.dataprovider._set_cached_df(pair, self.timeframe, df_analyzed) self.dataprovider._set_cached_df(pair, self.timeframe, df_analyzed)
@ -359,8 +360,10 @@ class Backtesting:
if sell.sell_flag: if sell.sell_flag:
trade.close_date = sell_candle_time trade.close_date = sell_candle_time
if(sell_row[SELL_TAG_IDX] is not None): if(sell_row[EXIT_TAG_IDX] is not None):
trade.sell_tag = sell_row[SELL_TAG_IDX] trade.exit_tag = sell_row[EXIT_TAG_IDX]
else:
trade.exit_tag = None
trade.sell_reason = sell.sell_reason trade.sell_reason = sell.sell_reason
trade_dur = int((trade.close_date_utc - trade.open_date_utc).total_seconds() // 60) trade_dur = int((trade.close_date_utc - trade.open_date_utc).total_seconds() // 60)
closerate = self._get_close_rate(sell_row, trade, sell, trade_dur) closerate = self._get_close_rate(sell_row, trade, sell, trade_dur)
@ -435,7 +438,7 @@ class Backtesting:
if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount): if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount):
# Enter trade # Enter trade
has_buy_tag = len(row) >= BUY_TAG_IDX + 1 has_buy_tag = len(row) >= BUY_TAG_IDX + 1
has_sell_tag = len(row) >= SELL_TAG_IDX + 1 has_exit_tag = len(row) >= EXIT_TAG_IDX + 1
trade = LocalTrade( trade = LocalTrade(
pair=pair, pair=pair,
open_rate=row[OPEN_IDX], open_rate=row[OPEN_IDX],
@ -446,7 +449,7 @@ class Backtesting:
fee_close=self.fee, fee_close=self.fee,
is_open=True, is_open=True,
buy_tag=row[BUY_TAG_IDX] if has_buy_tag else None, buy_tag=row[BUY_TAG_IDX] if has_buy_tag else None,
sell_tag=row[SELL_TAG_IDX] if has_sell_tag else None, exit_tag=row[EXIT_TAG_IDX] if has_exit_tag else None,
exchange='backtesting', exchange='backtesting',
) )
return trade return trade

View File

@ -54,6 +54,7 @@ def _get_line_header(first_column: str, stake_currency: str) -> List[str]:
f'Tot Profit {stake_currency}', 'Tot Profit %', 'Avg Duration', f'Tot Profit {stake_currency}', 'Tot Profit %', 'Avg Duration',
'Win Draw Loss Win%'] 'Win Draw Loss Win%']
def _get_line_header_sell(first_column: str, stake_currency: str) -> List[str]: def _get_line_header_sell(first_column: str, stake_currency: str) -> List[str]:
""" """
Generate header lines (goes in line with _generate_result_line()) Generate header lines (goes in line with _generate_result_line())
@ -134,12 +135,13 @@ def generate_pair_metrics(data: Dict[str, Dict], stake_currency: str, starting_b
tabular_data.append(_generate_result_line(results, starting_balance, 'TOTAL')) tabular_data.append(_generate_result_line(results, starting_balance, 'TOTAL'))
return tabular_data return tabular_data
def generate_tag_metrics(tag_type:str, data: Dict[str, Dict], stake_currency: str, starting_balance: int,
results: DataFrame, skip_nan: bool = False) -> List[Dict]: def generate_tag_metrics(tag_type: str,
starting_balance: int,
results: DataFrame,
skip_nan: bool = False) -> List[Dict]:
""" """
Generates and returns a list of metrics for the given tag trades and the results dataframe Generates and returns a list of metrics for the given tag trades and the results dataframe
:param data: Dict of <pair: dataframe> containing data that was used during backtesting.
:param stake_currency: stake-currency - used to correctly name headers
:param starting_balance: Starting balance :param starting_balance: Starting balance
:param results: Dataframe containing the backtest results :param results: Dataframe containing the backtest results
:param skip_nan: Print "left open" open trades :param skip_nan: Print "left open" open trades
@ -148,32 +150,6 @@ def generate_tag_metrics(tag_type:str, data: Dict[str, Dict], stake_currency: st
tabular_data = [] tabular_data = []
# for tag, count in results[tag_type].value_counts().iteritems():
# result = results.loc[results[tag_type] == tag]
#
# profit_mean = result['profit_ratio'].mean()
# profit_sum = result['profit_ratio'].sum()
# profit_total = profit_sum / max_open_trades
#
# tabular_data.append(
# {
# 'sell_reason': tag,
# 'trades': count,
# 'wins': len(result[result['profit_abs'] > 0]),
# 'draws': len(result[result['profit_abs'] == 0]),
# 'losses': len(result[result['profit_abs'] < 0]),
# 'profit_mean': profit_mean,
# 'profit_mean_pct': round(profit_mean * 100, 2),
# 'profit_sum': profit_sum,
# 'profit_sum_pct': round(profit_sum * 100, 2),
# 'profit_total_abs': result['profit_abs'].sum(),
# 'profit_total': profit_total,
# 'profit_total_pct': round(profit_total * 100, 2),
# }
# )
#
# tabular_data = []
for tag, count in results[tag_type].value_counts().iteritems(): for tag, count in results[tag_type].value_counts().iteritems():
result = results[results[tag_type] == tag] result = results[results[tag_type] == tag]
if skip_nan and result['profit_abs'].isnull().all(): if skip_nan and result['profit_abs'].isnull().all():
@ -188,6 +164,7 @@ def generate_tag_metrics(tag_type:str, data: Dict[str, Dict], stake_currency: st
tabular_data.append(_generate_result_line(results, starting_balance, 'TOTAL')) tabular_data.append(_generate_result_line(results, starting_balance, 'TOTAL'))
return tabular_data return tabular_data
def _generate_tag_result_line(result: DataFrame, starting_balance: int, first_column: str) -> Dict: def _generate_tag_result_line(result: DataFrame, starting_balance: int, first_column: str) -> Dict:
""" """
Generate one result dict, with "first_column" as key. Generate one result dict, with "first_column" as key.
@ -408,11 +385,9 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
starting_balance=starting_balance, starting_balance=starting_balance,
results=results, skip_nan=False) results=results, skip_nan=False)
buy_tag_results = generate_tag_metrics("buy_tag",btdata, stake_currency=stake_currency, buy_tag_results = generate_tag_metrics("buy_tag", starting_balance=starting_balance,
starting_balance=starting_balance,
results=results, skip_nan=False) results=results, skip_nan=False)
sell_tag_results = generate_tag_metrics("sell_tag",btdata, stake_currency=stake_currency, exit_tag_results = generate_tag_metrics("exit_tag", starting_balance=starting_balance,
starting_balance=starting_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,
@ -439,7 +414,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
'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_buy_tag': buy_tag_results,
'results_per_sell_tag': sell_tag_results, 'results_per_exit_tag': exit_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,
'total_trades': len(results), 'total_trades': len(results),
@ -609,6 +584,7 @@ def text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], stake_curren
] for t in sell_reason_stats] ] for t in sell_reason_stats]
return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right") return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right")
def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_currency: str) -> str: def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_currency: str) -> str:
""" """
Generates and returns a text table for the given backtest data and the results dataframe Generates and returns a text table for the given backtest data and the results dataframe
@ -621,18 +597,25 @@ def text_table_tags(tag_type:str, tag_results: List[Dict[str, Any]], stake_curre
else: else:
headers = _get_line_header_sell("TAG", stake_currency) headers = _get_line_header_sell("TAG", stake_currency)
floatfmt = _get_line_floatfmt(stake_currency) floatfmt = _get_line_floatfmt(stake_currency)
output = [[ output = [
t['key'], t['trades'], t['profit_mean_pct'], t['profit_sum_pct'], t['profit_total_abs'], [
t['profit_total_pct'], t['duration_avg'], t['key'] if t['key'] is not None and len(
_generate_wins_draws_losses(t['wins'], t['draws'], t['losses']) t['key']) > 0 else "OTHER",
] for t in tag_results] t['trades'],
t['profit_mean_pct'],
t['profit_sum_pct'],
t['profit_total_abs'],
t['profit_total_pct'],
t['duration_avg'],
_generate_wins_draws_losses(
t['wins'],
t['draws'],
t['losses'])] for t in tag_results]
# Ignore type as floatfmt does allow tuples but mypy does not know that # Ignore type as floatfmt does allow tuples but mypy does not know that
return tabulate(output, headers=headers, return tabulate(output, headers=headers,
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") floatfmt=floatfmt, tablefmt="orgtbl", stralign="right")
def text_table_strategy(strategy_results, stake_currency: str) -> str: def text_table_strategy(strategy_results, stake_currency: str) -> str:
""" """
Generate summary table per strategy Generate summary table per strategy
@ -752,14 +735,19 @@ 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)
table = text_table_tags(
table = text_table_tags("buy_tag", results['results_per_buy_tag'], stake_currency=stake_currency) "buy_tag",
results['results_per_buy_tag'],
stake_currency=stake_currency)
if isinstance(table, str) and len(table) > 0: if isinstance(table, str) and len(table) > 0:
print(' BUY TAG STATS '.center(len(table.splitlines()[0]), '=')) print(' BUY TAG STATS '.center(len(table.splitlines()[0]), '='))
print(table) print(table)
table = text_table_tags("sell_tag",results['results_per_sell_tag'], stake_currency=stake_currency) table = text_table_tags(
"exit_tag",
results['results_per_exit_tag'],
stake_currency=stake_currency)
if isinstance(table, str) and len(table) > 0: if isinstance(table, str) and len(table) > 0:
print(' SELL TAG STATS '.center(len(table.splitlines()[0]), '=')) print(' SELL TAG STATS '.center(len(table.splitlines()[0]), '='))
@ -771,10 +759,6 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '=')) print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '='))
print(table) print(table)
table = text_table_bt_results(results['left_open_trades'], stake_currency=stake_currency) table = text_table_bt_results(results['left_open_trades'], stake_currency=stake_currency)
if isinstance(table, str) and len(table) > 0: if isinstance(table, str) and len(table) > 0:
print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '=')) print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '='))
@ -785,12 +769,9 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
print(' SUMMARY METRICS '.center(len(table.splitlines()[0]), '=')) print(' SUMMARY METRICS '.center(len(table.splitlines()[0]), '='))
print(table) print(table)
if isinstance(table, str) and len(table) > 0: if isinstance(table, str) and len(table) > 0:
print('=' * len(table.splitlines()[0])) print('=' * len(table.splitlines()[0]))
print() print()

View File

@ -48,7 +48,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
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') buy_tag = get_column_def(cols, 'buy_tag', 'null')
sell_tag = get_column_def(cols, 'sell_tag', 'null') exit_tag = get_column_def(cols, 'exit_tag', 'null')
# If ticker-interval existed use that, else null. # If ticker-interval existed use that, else null.
if has_column(cols, 'ticker_interval'): if has_column(cols, 'ticker_interval'):
timeframe = get_column_def(cols, 'timeframe', 'ticker_interval') timeframe = get_column_def(cols, 'timeframe', 'ticker_interval')
@ -83,7 +83,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, sell_tag, max_rate, min_rate, sell_reason, sell_order_status, strategy, buy_tag, exit_tag,
timeframe, open_trade_value, close_profit_abs timeframe, open_trade_value, close_profit_abs
) )
select id, lower(exchange), pair, select id, lower(exchange), pair,
@ -99,7 +99,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, {sell_tag} sell_tag, {timeframe} timeframe, {strategy} strategy, {buy_tag} buy_tag, {exit_tag} exit_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
from {table_back_name} from {table_back_name}
""")) """))
@ -158,7 +158,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, 'sell_tag'): if not has_column(cols, 'exit_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

@ -258,7 +258,7 @@ class LocalTrade():
sell_order_status: str = '' sell_order_status: str = ''
strategy: str = '' strategy: str = ''
buy_tag: Optional[str] = None buy_tag: Optional[str] = None
sell_tag: Optional[str] = None exit_tag: Optional[str] = None
timeframe: Optional[int] = None timeframe: Optional[int] = None
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -325,8 +325,9 @@ class LocalTrade():
'profit_pct': round(self.close_profit * 100, 2) if self.close_profit else None, 'profit_pct': round(self.close_profit * 100, 2) if self.close_profit else None,
'profit_abs': self.close_profit_abs, 'profit_abs': self.close_profit_abs,
'sell_reason': (f' ({self.sell_reason})' if self.sell_reason else ''), #+str(self.sell_reason) ## CHANGE TO BUY TAG IF NEEDED # +str(self.sell_reason) ## CHANGE TO BUY TAG IF NEEDED
'sell_tag': (f' ({self.sell_tag})' if self.sell_tag else '') , 'sell_reason': (f' ({self.sell_reason})' if self.sell_reason else ''),
'exit_tag': (f' ({self.exit_tag})' if self.exit_tag else ''),
'sell_order_status': self.sell_order_status, 'sell_order_status': self.sell_order_status,
'stop_loss_abs': self.stop_loss, 'stop_loss_abs': self.stop_loss,
'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None, 'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None,
@ -708,7 +709,7 @@ class Trade(_DECL_BASE, LocalTrade):
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) buy_tag = Column(String(100), nullable=True)
sell_tag = Column(String(100), nullable=True) exit_tag = Column(String(100), nullable=True)
timeframe = Column(Integer, nullable=True) timeframe = Column(Integer, nullable=True)
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -878,7 +879,7 @@ class Trade(_DECL_BASE, LocalTrade):
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(Trade.is_open.is_(False))\ ).filter(Trade.is_open.is_(False))\
.filter(Trade.pair.lower() == pair.lower()) \ .filter(Trade.pair == pair) \
.order_by(desc('profit_sum_abs')) \ .order_by(desc('profit_sum_abs')) \
.all() .all()
else: else:
@ -888,13 +889,13 @@ class Trade(_DECL_BASE, LocalTrade):
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(Trade.is_open.is_(False))\ ).filter(Trade.is_open.is_(False))\
.group_by(Trade.pair) \ .group_by(Trade.buy_tag) \
.order_by(desc('profit_sum_abs')) \ .order_by(desc('profit_sum_abs')) \
.all() .all()
return [ return [
{ {
'buy_tag': buy_tag, 'buy_tag': buy_tag if buy_tag is not None else "Other",
'profit': profit, 'profit': profit,
'profit_abs': profit_abs, 'profit_abs': profit_abs,
'count': count 'count': count
@ -903,81 +904,102 @@ class Trade(_DECL_BASE, LocalTrade):
] ]
@staticmethod @staticmethod
def get_sell_tag_performance(pair: str) -> List[Dict[str, Any]]: def get_exit_tag_performance(pair: str) -> List[Dict[str, Any]]:
""" """
Returns List of dicts containing all Trades, based on sell tag performance Returns List of dicts containing all Trades, based on exit 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
NOTE: Not supported in Backtesting. NOTE: Not supported in Backtesting.
""" """
if(pair is not None): if(pair is not None):
tag_perf = Trade.query.with_entities( tag_perf = Trade.query.with_entities(
Trade.sell_tag, Trade.exit_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(Trade.is_open.is_(False))\ ).filter(Trade.is_open.is_(False))\
.filter(Trade.pair.lower() == pair.lower()) \ .filter(Trade.pair == pair) \
.order_by(desc('profit_sum_abs')) \ .order_by(desc('profit_sum_abs')) \
.all() .all()
else: else:
tag_perf = Trade.query.with_entities( tag_perf = Trade.query.with_entities(
Trade.sell_tag, Trade.exit_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(Trade.is_open.is_(False))\ ).filter(Trade.is_open.is_(False))\
.group_by(Trade.pair) \ .group_by(Trade.exit_tag) \
.order_by(desc('profit_sum_abs')) \ .order_by(desc('profit_sum_abs')) \
.all() .all()
return [ return [
{ {
'sell_tag': sell_tag, 'exit_tag': exit_tag if exit_tag is not None else "Other",
'profit': profit, 'profit': profit,
'profit_abs': profit_abs, 'profit_abs': profit_abs,
'count': count 'count': count
} }
for sell_tag, profit, profit_abs, count in tag_perf for exit_tag, profit, profit_abs, count in tag_perf
] ]
@staticmethod @staticmethod
def get_mix_tag_performance(pair: str) -> List[Dict[str, Any]]: def get_mix_tag_performance(pair: str) -> List[Dict[str, Any]]:
""" """
Returns List of dicts containing all Trades, based on buy_tag + sell_tag performance Returns List of dicts containing all Trades, based on buy_tag + exit_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
NOTE: Not supported in Backtesting. NOTE: Not supported in Backtesting.
""" """
if(pair is not None): if(pair is not None):
tag_perf = Trade.query.with_entities( tag_perf = Trade.query.with_entities(
Trade.id,
Trade.buy_tag, Trade.buy_tag,
Trade.sell_tag, Trade.exit_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(Trade.is_open.is_(False))\ ).filter(Trade.is_open.is_(False))\
.filter(Trade.pair.lower() == pair.lower()) \ .filter(Trade.pair == pair) \
.order_by(desc('profit_sum_abs')) \
.all()
else:
tag_perf = Trade.query.with_entities(
Trade.buy_tag,
Trade.sell_tag,
func.sum(Trade.close_profit).label('profit_sum'),
func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
func.count(Trade.pair).label('count')
).filter(Trade.is_open.is_(False))\
.group_by(Trade.pair) \
.order_by(desc('profit_sum_abs')) \ .order_by(desc('profit_sum_abs')) \
.all() .all()
return [ else:
{ 'mix_tag': str(buy_tag) + " " +str(sell_tag), tag_perf = Trade.query.with_entities(
Trade.id,
Trade.buy_tag,
Trade.exit_tag,
func.sum(Trade.close_profit).label('profit_sum'),
func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
func.count(Trade.pair).label('count')
).filter(Trade.is_open.is_(False))\
.group_by(Trade.id) \
.order_by(desc('profit_sum_abs')) \
.all()
return_list = []
for id, buy_tag, exit_tag, profit, profit_abs, count in tag_perf:
buy_tag = buy_tag if buy_tag is not None else "Other"
exit_tag = exit_tag if exit_tag is not None else "Other"
if(exit_tag is not None and buy_tag is not None):
mix_tag = buy_tag + " " + exit_tag
i = 0
if not any(item["mix_tag"] == mix_tag for item in return_list):
return_list.append({'mix_tag': mix_tag,
'profit': profit, 'profit': profit,
'profit_abs': profit_abs, 'profit_abs': profit_abs,
'count': count 'count': count})
} else:
for buy_tag, sell_tag, profit, profit_abs, count in tag_perf while i < len(return_list):
] if return_list[i]["mix_tag"] == mix_tag:
print("item below")
print(return_list[i])
return_list[i] = {
'mix_tag': mix_tag,
'profit': profit + return_list[i]["profit"],
'profit_abs': profit_abs + return_list[i]["profit_abs"],
'count': 1 + return_list[i]["count"]}
i += 1
return return_list
@staticmethod @staticmethod
def get_best_pair(start_date: datetime = datetime.fromtimestamp(0)): def get_best_pair(start_date: datetime = datetime.fromtimestamp(0)):

View File

@ -696,16 +696,15 @@ class RPC:
[x.update({'profit': round(x['profit'] * 100, 2)}) for x in buy_tags] [x.update({'profit': round(x['profit'] * 100, 2)}) for x in buy_tags]
return buy_tags return buy_tags
def _rpc_exit_tag_performance(self, pair: str) -> List[Dict[str, Any]]:
def _rpc_sell_tag_performance(self, pair: str) -> List[Dict[str, Any]]:
""" """
Handler for sell tag performance. Handler for sell tag performance.
Shows a performance statistic from finished trades Shows a performance statistic from finished trades
""" """
sell_tags = Trade.get_sell_tag_performance(pair) exit_tags = Trade.get_exit_tag_performance(pair)
# Round and convert to % # Round and convert to %
[x.update({'profit': round(x['profit'] * 100, 2)}) for x in sell_tags] [x.update({'profit': round(x['profit'] * 100, 2)}) for x in exit_tags]
return sell_tags return exit_tags
def _rpc_mix_tag_performance(self, pair: str) -> List[Dict[str, Any]]: def _rpc_mix_tag_performance(self, pair: str) -> List[Dict[str, Any]]:
""" """

View File

@ -154,7 +154,7 @@ class Telegram(RPCHandler):
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', self._buy_tag_performance),
CommandHandler('sells', self._sell_tag_performance), CommandHandler('sells', self._exit_tag_performance),
CommandHandler('mix_tags', self._mix_tag_performance), CommandHandler('mix_tags', self._mix_tag_performance),
CommandHandler('stats', self._stats), CommandHandler('stats', self._stats),
CommandHandler('daily', self._daily), CommandHandler('daily', self._daily),
@ -178,7 +178,7 @@ class Telegram(RPCHandler):
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._performance, pattern='update_buy_tag_performance'), CallbackQueryHandler(self._performance, pattern='update_buy_tag_performance'),
CallbackQueryHandler(self._performance, pattern='update_sell_tag_performance'), CallbackQueryHandler(self._performance, pattern='update_exit_tag_performance'),
CallbackQueryHandler(self._performance, pattern='update_mix_tag_performance'), CallbackQueryHandler(self._performance, pattern='update_mix_tag_performance'),
CallbackQueryHandler(self._count, pattern='update_count'), CallbackQueryHandler(self._count, pattern='update_count'),
CallbackQueryHandler(self._forcebuy_inline), CallbackQueryHandler(self._forcebuy_inline),
@ -242,6 +242,7 @@ class Telegram(RPCHandler):
msg['duration'] = msg['close_date'].replace( msg['duration'] = msg['close_date'].replace(
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['tags'] = self._get_tags_string(msg)
msg['emoji'] = self._get_sell_emoji(msg) msg['emoji'] = self._get_sell_emoji(msg)
@ -258,6 +259,7 @@ class Telegram(RPCHandler):
message = ("{emoji} *{exchange}:* Selling {pair} (#{trade_id})\n" message = ("{emoji} *{exchange}:* Selling {pair} (#{trade_id})\n"
"*Profit:* `{profit_percent:.2f}%{profit_extra}`\n" "*Profit:* `{profit_percent:.2f}%{profit_extra}`\n"
"{tags}"
"*Sell Reason:* `{sell_reason}`\n" "*Sell Reason:* `{sell_reason}`\n"
"*Duration:* `{duration} ({duration_min:.1f} min)`\n" "*Duration:* `{duration} ({duration_min:.1f} min)`\n"
"*Amount:* `{amount:.8f}`\n" "*Amount:* `{amount:.8f}`\n"
@ -265,46 +267,6 @@ class Telegram(RPCHandler):
"*Current Rate:* `{current_rate:.8f}`\n" "*Current Rate:* `{current_rate:.8f}`\n"
"*Close Rate:* `{limit:.8f}`").format(**msg) "*Close Rate:* `{limit:.8f}`").format(**msg)
sell_tag =None
if("sell_tag" in msg.keys()):
sell_tag = msg['sell_tag']
buy_tag =None
if("buy_tag" in msg.keys()):
buy_tag = msg['buy_tag']
if sell_tag is not None and buy_tag is not None:
message = ("{emoji} *{exchange}:* Selling {pair} (#{trade_id})\n"
"*Profit:* `{profit_percent:.2f}%{profit_extra}`\n"
"*Buy Tag:* `{buy_tag}`\n"
"*Sell Tag:* `{sell_tag}`\n"
"*Sell Reason:* `{sell_reason}`\n"
"*Duration:* `{duration} ({duration_min:.1f} min)`\n"
"*Amount:* `{amount:.8f}`\n"
"*Open Rate:* `{open_rate:.8f}`\n"
"*Current Rate:* `{current_rate:.8f}`\n"
"*Close Rate:* `{limit:.8f}`").format(**msg)
elif sell_tag is None and buy_tag is not None:
message = ("{emoji} *{exchange}:* Selling {pair} (#{trade_id})\n"
"*Profit:* `{profit_percent:.2f}%{profit_extra}`\n"
"*Buy Tag:* `{buy_tag}`\n"
"*Sell Reason:* `{sell_reason}`\n"
"*Duration:* `{duration} ({duration_min:.1f} min)`\n"
"*Amount:* `{amount:.8f}`\n"
"*Open Rate:* `{open_rate:.8f}`\n"
"*Current Rate:* `{current_rate:.8f}`\n"
"*Close Rate:* `{limit:.8f}`").format(**msg)
elif sell_tag is not None and buy_tag is None:
message = ("{emoji} *{exchange}:* Selling {pair} (#{trade_id})\n"
"*Profit:* `{profit_percent:.2f}%{profit_extra}`\n"
"*Sell Tag:* `{sell_tag}`\n"
"*Sell Reason:* `{sell_reason}`\n"
"*Duration:* `{duration} ({duration_min:.1f} min)`\n"
"*Amount:* `{amount:.8f}`\n"
"*Open Rate:* `{open_rate:.8f}`\n"
"*Current Rate:* `{current_rate:.8f}`\n"
"*Close Rate:* `{limit:.8f}`").format(**msg)
return message return message
def compose_message(self, msg: Dict[str, Any], msg_type: RPCMessageType) -> str: def compose_message(self, msg: Dict[str, Any], msg_type: RPCMessageType) -> str:
@ -393,6 +355,18 @@ class Telegram(RPCHandler):
else: else:
return "\N{CROSS MARK}" return "\N{CROSS MARK}"
def _get_tags_string(self, msg):
"""
Get string lines for buy/sell tags to display when a sell is made
"""
tag_lines = ""
if ("buy_tag" in msg.keys() and msg['buy_tag'] is not None):
tag_lines += ("*Buy Tag:* `{buy_tag}`\n").format(msg['buy_tag'])
if ("exit_tag" in msg.keys() and msg['exit_tag'] is not None):
tag_lines += ("*Sell Tag:* `{exit_tag}`\n").format(msg['exit_tag'])
return tag_lines
@authorized_only @authorized_only
def _status(self, update: Update, context: CallbackContext) -> None: def _status(self, update: Update, context: CallbackContext) -> None:
""" """
@ -425,7 +399,7 @@ class Telegram(RPCHandler):
"*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 "", "*Buy Tag:* `{buy_tag}`" if r['buy_tag'] else "",
"*Sell Tag:* `{sell_tag}`" if r['sell_tag'] else "", "*Sell Tag:* `{exit_tag}`" if r['exit_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}`",
@ -928,7 +902,7 @@ class Telegram(RPCHandler):
pair = context.args[0] pair = context.args[0]
trades = self._rpc._rpc_buy_tag_performance(pair) trades = self._rpc._rpc_buy_tag_performance(pair)
output = "<b>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['buy_tag']}\t"
@ -949,7 +923,7 @@ class Telegram(RPCHandler):
self._send_msg(str(e)) self._send_msg(str(e))
@authorized_only @authorized_only
def _sell_tag_performance(self, update: Update, context: CallbackContext) -> None: def _exit_tag_performance(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /sells. Handler for /sells.
Shows a performance statistic from finished trades Shows a performance statistic from finished trades
@ -962,11 +936,11 @@ class Telegram(RPCHandler):
if context.args: if context.args:
pair = context.args[0] pair = context.args[0]
trades = self._rpc._rpc_sell_tag_performance(pair) trades = self._rpc._rpc_exit_tag_performance(pair)
output = "<b>Performance:</b>\n" output = "<b>Sell 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['sell_tag']}\t" f"{i+1}.\t <code>{trade['exit_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']:.2f}%) " f"({trade['profit']:.2f}%) "
f"({trade['count']})</code>\n") f"({trade['count']})</code>\n")
@ -978,7 +952,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_sell_tag_performance", reload_able=True, callback_path="update_exit_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))
@ -998,8 +972,9 @@ class Telegram(RPCHandler):
pair = context.args[0] pair = context.args[0]
trades = self._rpc._rpc_mix_tag_performance(pair) trades = self._rpc._rpc_mix_tag_performance(pair)
output = "<b>Performance:</b>\n" output = "<b>Mix Tag Performance:</b>\n"
for i, trade in enumerate(trades): for i, trade in enumerate(trades):
print(str(trade))
stat_line = ( stat_line = (
f"{i+1}.\t <code>{trade['mix_tag']}\t" f"{i+1}.\t <code>{trade['mix_tag']}\t"
f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} "

View File

@ -500,7 +500,7 @@ class IStrategy(ABC, HyperStrategyMixin):
dataframe['buy'] = 0 dataframe['buy'] = 0
dataframe['sell'] = 0 dataframe['sell'] = 0
dataframe['buy_tag'] = None dataframe['buy_tag'] = None
dataframe['sell_tag'] = None dataframe['exit_tag'] = None
# Other Defs in strategy that want to be called every loop here # Other Defs in strategy that want to be called every loop here
# twitter_sell = self.watch_twitter_feed(dataframe, metadata) # twitter_sell = self.watch_twitter_feed(dataframe, metadata)
@ -613,7 +613,7 @@ class IStrategy(ABC, HyperStrategyMixin):
sell = latest[SignalType.SELL.value] == 1 sell = latest[SignalType.SELL.value] == 1
buy_tag = latest.get(SignalTagType.BUY_TAG.value, None) buy_tag = latest.get(SignalTagType.BUY_TAG.value, None)
sell_tag = latest.get(SignalTagType.SELL_TAG.value, None) exit_tag = latest.get(SignalTagType.EXIT_TAG.value, None)
logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', logger.debug('trigger: %s (pair=%s) buy=%s sell=%s',
latest['date'], pair, str(buy), str(sell)) latest['date'], pair, str(buy), str(sell))
@ -622,8 +622,8 @@ class IStrategy(ABC, HyperStrategyMixin):
current_time=datetime.now(timezone.utc), current_time=datetime.now(timezone.utc),
timeframe_seconds=timeframe_seconds, timeframe_seconds=timeframe_seconds,
buy=buy): buy=buy):
return False, sell, buy_tag, sell_tag return False, sell, buy_tag, exit_tag
return buy, sell, buy_tag, sell_tag return buy, sell, buy_tag, exit_tag
def ignore_expired_candle(self, latest_date: datetime, current_time: datetime, def ignore_expired_candle(self, latest_date: datetime, current_time: datetime,
timeframe_seconds: int, buy: bool): timeframe_seconds: int, buy: bool):