Fixed flake 8, changed sell_tag to exit_tag and fixed telegram functions
This commit is contained in:
parent
0bb7ea10ab
commit
69a59cdf37
@ -30,7 +30,7 @@ BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
|
||||
'fee_open', 'fee_close', 'trade_duration',
|
||||
'profit_ratio', 'profit_abs', 'sell_reason',
|
||||
'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:
|
||||
|
@ -14,4 +14,4 @@ class SignalTagType(Enum):
|
||||
Enum for signal columns
|
||||
"""
|
||||
BUY_TAG = "buy_tag"
|
||||
SELL_TAG = "sell_tag"
|
||||
EXIT_TAG = "exit_tag"
|
||||
|
@ -420,7 +420,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
return False
|
||||
|
||||
# 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,
|
||||
self.strategy.timeframe,
|
||||
analyzed_df
|
||||
@ -700,15 +700,14 @@ class FreqtradeBot(LoggingMixin):
|
||||
logger.debug('Handling %s ...', trade)
|
||||
|
||||
(buy, sell) = (False, False)
|
||||
|
||||
sell_tag=None
|
||||
exit_tag = None
|
||||
|
||||
if (self.config.get('use_sell_signal', True) or
|
||||
self.config.get('ignore_roi_if_buy_signal', False)):
|
||||
analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair,
|
||||
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,
|
||||
self.strategy.timeframe,
|
||||
analyzed_df
|
||||
@ -716,7 +715,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
logger.debug('checking 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
|
||||
|
||||
logger.debug('Found no sell signal for %s.', trade)
|
||||
@ -854,7 +853,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
f"for pair {trade.pair}.")
|
||||
|
||||
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
|
||||
"""
|
||||
@ -865,8 +864,9 @@ class FreqtradeBot(LoggingMixin):
|
||||
)
|
||||
|
||||
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"}')
|
||||
self.execute_trade_exit(trade, exit_rate, should_sell,sell_tag)
|
||||
logger.info(
|
||||
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 False
|
||||
|
||||
@ -1067,7 +1067,12 @@ class FreqtradeBot(LoggingMixin):
|
||||
raise DependencyException(
|
||||
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
|
||||
:param trade: Trade instance
|
||||
@ -1144,8 +1149,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
trade.sell_order_status = ''
|
||||
trade.close_rate_requested = limit
|
||||
trade.sell_reason = sell_reason.sell_reason
|
||||
if(sell_tag is not None):
|
||||
trade.sell_tag = sell_tag
|
||||
if(exit_tag is not None):
|
||||
trade.exit_tag = exit_tag
|
||||
# In case of market sell orders the order can be closed immediately
|
||||
if order.get('status', 'unknown') in ('closed', 'expired'):
|
||||
self.update_trade_state(trade, trade.open_order_id, order)
|
||||
@ -1187,7 +1192,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
'profit_amount': profit_trade,
|
||||
'profit_ratio': profit_ratio,
|
||||
'sell_reason': trade.sell_reason,
|
||||
'sell_tag': trade.sell_tag,
|
||||
'exit_tag': trade.exit_tag,
|
||||
'open_date': trade.open_date,
|
||||
'close_date': trade.close_date or datetime.utcnow(),
|
||||
'stake_currency': self.config['stake_currency'],
|
||||
@ -1231,7 +1236,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
'profit_amount': profit_trade,
|
||||
'profit_ratio': profit_ratio,
|
||||
'sell_reason': trade.sell_reason,
|
||||
'sell_tag': trade.sell_tag,
|
||||
'exit_tag': trade.exit_tag,
|
||||
'open_date': trade.open_date,
|
||||
'close_date': trade.close_date or datetime.now(timezone.utc),
|
||||
'stake_currency': self.config['stake_currency'],
|
||||
|
@ -44,7 +44,8 @@ SELL_IDX = 4
|
||||
LOW_IDX = 5
|
||||
HIGH_IDX = 6
|
||||
BUY_TAG_IDX = 7
|
||||
SELL_TAG_IDX = 8
|
||||
EXIT_TAG_IDX = 8
|
||||
|
||||
|
||||
class Backtesting:
|
||||
"""
|
||||
@ -247,7 +248,7 @@ class Backtesting:
|
||||
"""
|
||||
# Every change to this headers list must evaluate further usages of the resulting tuple
|
||||
# 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 = {}
|
||||
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[:, 'sell'] = 0 # cleanup if sell_signal 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(
|
||||
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[:, 'sell'] = df_analyzed.loc[:, 'sell'].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
|
||||
self.dataprovider._set_cached_df(pair, self.timeframe, df_analyzed)
|
||||
@ -359,8 +360,10 @@ class Backtesting:
|
||||
|
||||
if sell.sell_flag:
|
||||
trade.close_date = sell_candle_time
|
||||
if(sell_row[SELL_TAG_IDX] is not None):
|
||||
trade.sell_tag = sell_row[SELL_TAG_IDX]
|
||||
if(sell_row[EXIT_TAG_IDX] is not None):
|
||||
trade.exit_tag = sell_row[EXIT_TAG_IDX]
|
||||
else:
|
||||
trade.exit_tag = None
|
||||
trade.sell_reason = sell.sell_reason
|
||||
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)
|
||||
@ -435,7 +438,7 @@ class Backtesting:
|
||||
if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount):
|
||||
# Enter trade
|
||||
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(
|
||||
pair=pair,
|
||||
open_rate=row[OPEN_IDX],
|
||||
@ -446,7 +449,7 @@ class Backtesting:
|
||||
fee_close=self.fee,
|
||||
is_open=True,
|
||||
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',
|
||||
)
|
||||
return trade
|
||||
|
@ -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',
|
||||
'Win Draw Loss Win%']
|
||||
|
||||
|
||||
def _get_line_header_sell(first_column: str, stake_currency: str) -> List[str]:
|
||||
"""
|
||||
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'))
|
||||
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
|
||||
: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 results: Dataframe containing the backtest results
|
||||
: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 = []
|
||||
|
||||
# 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():
|
||||
result = results[results[tag_type] == tag]
|
||||
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'))
|
||||
return tabular_data
|
||||
|
||||
|
||||
def _generate_tag_result_line(result: DataFrame, starting_balance: int, first_column: str) -> Dict:
|
||||
"""
|
||||
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,
|
||||
results=results, skip_nan=False)
|
||||
|
||||
buy_tag_results = generate_tag_metrics("buy_tag",btdata, stake_currency=stake_currency,
|
||||
starting_balance=starting_balance,
|
||||
buy_tag_results = generate_tag_metrics("buy_tag", starting_balance=starting_balance,
|
||||
results=results, skip_nan=False)
|
||||
sell_tag_results = generate_tag_metrics("sell_tag",btdata, stake_currency=stake_currency,
|
||||
starting_balance=starting_balance,
|
||||
exit_tag_results = generate_tag_metrics("exit_tag", starting_balance=starting_balance,
|
||||
results=results, skip_nan=False)
|
||||
|
||||
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,
|
||||
'results_per_pair': pair_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,
|
||||
'left_open_trades': left_open_results,
|
||||
'total_trades': len(results),
|
||||
@ -609,30 +584,38 @@ def text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], stake_curren
|
||||
] for t in sell_reason_stats]
|
||||
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
|
||||
:param pair_results: List of Dictionaries - one entry per pair + final TOTAL row
|
||||
:param stake_currency: stake-currency - used to correctly name headers
|
||||
:return: pretty printed table with tabulate as string
|
||||
"""
|
||||
if(tag_type=="buy_tag"):
|
||||
if(tag_type == "buy_tag"):
|
||||
headers = _get_line_header("TAG", stake_currency)
|
||||
else:
|
||||
headers = _get_line_header_sell("TAG", stake_currency)
|
||||
floatfmt = _get_line_floatfmt(stake_currency)
|
||||
output = [[
|
||||
t['key'], 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]
|
||||
output = [
|
||||
[
|
||||
t['key'] if t['key'] is not None and len(
|
||||
t['key']) > 0 else "OTHER",
|
||||
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
|
||||
return tabulate(output, headers=headers,
|
||||
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right")
|
||||
|
||||
|
||||
|
||||
|
||||
def text_table_strategy(strategy_results, stake_currency: str) -> str:
|
||||
"""
|
||||
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(table)
|
||||
|
||||
|
||||
table = text_table_tags("buy_tag", results['results_per_buy_tag'], stake_currency=stake_currency)
|
||||
table = text_table_tags(
|
||||
"buy_tag",
|
||||
results['results_per_buy_tag'],
|
||||
stake_currency=stake_currency)
|
||||
|
||||
if isinstance(table, str) and len(table) > 0:
|
||||
print(' BUY TAG STATS '.center(len(table.splitlines()[0]), '='))
|
||||
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:
|
||||
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(table)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
table = text_table_bt_results(results['left_open_trades'], stake_currency=stake_currency)
|
||||
if isinstance(table, str) and len(table) > 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(table)
|
||||
|
||||
|
||||
|
||||
if isinstance(table, str) and len(table) > 0:
|
||||
print('=' * len(table.splitlines()[0]))
|
||||
|
||||
|
||||
print()
|
||||
|
||||
|
||||
|
@ -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')
|
||||
strategy = get_column_def(cols, 'strategy', '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 has_column(cols, '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,
|
||||
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
|
||||
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
|
||||
)
|
||||
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,
|
||||
{max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason,
|
||||
{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
|
||||
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')
|
||||
|
||||
# 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}')
|
||||
migrate_trades_table(decl_base, inspector, engine, table_back_name, cols)
|
||||
# Reread columns - the above recreated the table!
|
||||
|
@ -258,7 +258,7 @@ class LocalTrade():
|
||||
sell_order_status: str = ''
|
||||
strategy: str = ''
|
||||
buy_tag: Optional[str] = None
|
||||
sell_tag: Optional[str] = None
|
||||
exit_tag: Optional[str] = None
|
||||
timeframe: Optional[int] = None
|
||||
|
||||
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_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
|
||||
'sell_tag': (f' ({self.sell_tag})' if self.sell_tag else '') ,
|
||||
# +str(self.sell_reason) ## CHANGE TO BUY TAG IF NEEDED
|
||||
'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,
|
||||
'stop_loss_abs': self.stop_loss,
|
||||
'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)
|
||||
strategy = 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)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
@ -878,7 +879,7 @@ class Trade(_DECL_BASE, LocalTrade):
|
||||
func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
|
||||
func.count(Trade.pair).label('count')
|
||||
).filter(Trade.is_open.is_(False))\
|
||||
.filter(Trade.pair.lower() == pair.lower()) \
|
||||
.filter(Trade.pair == pair) \
|
||||
.order_by(desc('profit_sum_abs')) \
|
||||
.all()
|
||||
else:
|
||||
@ -888,13 +889,13 @@ class Trade(_DECL_BASE, LocalTrade):
|
||||
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) \
|
||||
.group_by(Trade.buy_tag) \
|
||||
.order_by(desc('profit_sum_abs')) \
|
||||
.all()
|
||||
|
||||
return [
|
||||
{
|
||||
'buy_tag': buy_tag,
|
||||
'buy_tag': buy_tag if buy_tag is not None else "Other",
|
||||
'profit': profit,
|
||||
'profit_abs': profit_abs,
|
||||
'count': count
|
||||
@ -903,81 +904,102 @@ class Trade(_DECL_BASE, LocalTrade):
|
||||
]
|
||||
|
||||
@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
|
||||
NOTE: Not supported in Backtesting.
|
||||
"""
|
||||
if(pair is not None):
|
||||
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_abs).label('profit_sum_abs'),
|
||||
func.count(Trade.pair).label('count')
|
||||
).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.sell_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.pair) \
|
||||
.group_by(Trade.exit_tag) \
|
||||
.order_by(desc('profit_sum_abs')) \
|
||||
.all()
|
||||
|
||||
return [
|
||||
{
|
||||
'sell_tag': sell_tag,
|
||||
'exit_tag': exit_tag if exit_tag is not None else "Other",
|
||||
'profit': profit,
|
||||
'profit_abs': profit_abs,
|
||||
'count': count
|
||||
}
|
||||
for sell_tag, profit, profit_abs, count in tag_perf
|
||||
for exit_tag, profit, profit_abs, count in tag_perf
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
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
|
||||
NOTE: Not supported in Backtesting.
|
||||
"""
|
||||
if(pair is not None):
|
||||
tag_perf = Trade.query.with_entities(
|
||||
Trade.id,
|
||||
Trade.buy_tag,
|
||||
Trade.sell_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))\
|
||||
.filter(Trade.pair.lower() == pair.lower()) \
|
||||
.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) \
|
||||
.filter(Trade.pair == pair) \
|
||||
.order_by(desc('profit_sum_abs')) \
|
||||
.all()
|
||||
|
||||
return [
|
||||
{ 'mix_tag': str(buy_tag) + " " +str(sell_tag),
|
||||
else:
|
||||
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_abs': profit_abs,
|
||||
'count': count
|
||||
}
|
||||
for buy_tag, sell_tag, profit, profit_abs, count in tag_perf
|
||||
]
|
||||
'count': count})
|
||||
else:
|
||||
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
|
||||
def get_best_pair(start_date: datetime = datetime.fromtimestamp(0)):
|
||||
|
@ -696,16 +696,15 @@ class RPC:
|
||||
[x.update({'profit': round(x['profit'] * 100, 2)}) for x in buy_tags]
|
||||
return buy_tags
|
||||
|
||||
|
||||
def _rpc_sell_tag_performance(self, pair: str) -> List[Dict[str, Any]]:
|
||||
def _rpc_exit_tag_performance(self, pair: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Handler for sell tag performance.
|
||||
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 %
|
||||
[x.update({'profit': round(x['profit'] * 100, 2)}) for x in sell_tags]
|
||||
return sell_tags
|
||||
[x.update({'profit': round(x['profit'] * 100, 2)}) for x in exit_tags]
|
||||
return exit_tags
|
||||
|
||||
def _rpc_mix_tag_performance(self, pair: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
|
@ -108,7 +108,7 @@ class Telegram(RPCHandler):
|
||||
r'/trades$', r'/performance$', r'/daily$', r'/daily \d+$',
|
||||
r'/profit$', r'/profit \d+',
|
||||
r'/stats$', r'/count$', r'/locks$', r'/balance$',
|
||||
r'/buys',r'/sells',r'/mix_tags',
|
||||
r'/buys', r'/sells', r'/mix_tags',
|
||||
r'/stopbuy$', r'/reload_config$', r'/show_config$',
|
||||
r'/logs$', r'/whitelist$', r'/blacklist$', r'/edge$',
|
||||
r'/forcebuy$', r'/help$', r'/version$']
|
||||
@ -154,7 +154,7 @@ class Telegram(RPCHandler):
|
||||
CommandHandler('delete', self._delete_trade),
|
||||
CommandHandler('performance', self._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('stats', self._stats),
|
||||
CommandHandler('daily', self._daily),
|
||||
@ -178,7 +178,7 @@ class Telegram(RPCHandler):
|
||||
CallbackQueryHandler(self._balance, pattern='update_balance'),
|
||||
CallbackQueryHandler(self._performance, pattern='update_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._count, pattern='update_count'),
|
||||
CallbackQueryHandler(self._forcebuy_inline),
|
||||
@ -242,6 +242,7 @@ class Telegram(RPCHandler):
|
||||
msg['duration'] = msg['close_date'].replace(
|
||||
microsecond=0) - msg['open_date'].replace(microsecond=0)
|
||||
msg['duration_min'] = msg['duration'].total_seconds() / 60
|
||||
msg['tags'] = self._get_tags_string(msg)
|
||||
|
||||
msg['emoji'] = self._get_sell_emoji(msg)
|
||||
|
||||
@ -258,6 +259,7 @@ class Telegram(RPCHandler):
|
||||
|
||||
message = ("{emoji} *{exchange}:* Selling {pair} (#{trade_id})\n"
|
||||
"*Profit:* `{profit_percent:.2f}%{profit_extra}`\n"
|
||||
"{tags}"
|
||||
"*Sell Reason:* `{sell_reason}`\n"
|
||||
"*Duration:* `{duration} ({duration_min:.1f} min)`\n"
|
||||
"*Amount:* `{amount:.8f}`\n"
|
||||
@ -265,46 +267,6 @@ class Telegram(RPCHandler):
|
||||
"*Current Rate:* `{current_rate:.8f}`\n"
|
||||
"*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
|
||||
|
||||
def compose_message(self, msg: Dict[str, Any], msg_type: RPCMessageType) -> str:
|
||||
@ -393,6 +355,18 @@ class Telegram(RPCHandler):
|
||||
else:
|
||||
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
|
||||
def _status(self, update: Update, context: CallbackContext) -> None:
|
||||
"""
|
||||
@ -425,7 +399,7 @@ class Telegram(RPCHandler):
|
||||
"*Current Pair:* {pair}",
|
||||
"*Amount:* `{amount} ({stake_amount} {base_currency})`",
|
||||
"*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}`",
|
||||
"*Close Rate:* `{close_rate}`" if r['close_rate'] else "",
|
||||
"*Current Rate:* `{current_rate:.8f}`",
|
||||
@ -923,12 +897,12 @@ class Telegram(RPCHandler):
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
pair=None
|
||||
pair = None
|
||||
if context.args:
|
||||
pair = context.args[0]
|
||||
|
||||
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):
|
||||
stat_line = (
|
||||
f"{i+1}.\t <code>{trade['buy_tag']}\t"
|
||||
@ -949,7 +923,7 @@ class Telegram(RPCHandler):
|
||||
self._send_msg(str(e))
|
||||
|
||||
@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.
|
||||
Shows a performance statistic from finished trades
|
||||
@ -958,15 +932,15 @@ class Telegram(RPCHandler):
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
pair=None
|
||||
pair = None
|
||||
if context.args:
|
||||
pair = context.args[0]
|
||||
|
||||
trades = self._rpc._rpc_sell_tag_performance(pair)
|
||||
output = "<b>Performance:</b>\n"
|
||||
trades = self._rpc._rpc_exit_tag_performance(pair)
|
||||
output = "<b>Sell Tag Performance:</b>\n"
|
||||
for i, trade in enumerate(trades):
|
||||
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"({trade['profit']:.2f}%) "
|
||||
f"({trade['count']})</code>\n")
|
||||
@ -978,7 +952,7 @@ class Telegram(RPCHandler):
|
||||
output += stat_line
|
||||
|
||||
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)
|
||||
except RPCException as e:
|
||||
self._send_msg(str(e))
|
||||
@ -993,13 +967,14 @@ class Telegram(RPCHandler):
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
pair=None
|
||||
pair = None
|
||||
if context.args:
|
||||
pair = context.args[0]
|
||||
|
||||
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):
|
||||
print(str(trade))
|
||||
stat_line = (
|
||||
f"{i+1}.\t <code>{trade['mix_tag']}\t"
|
||||
f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} "
|
||||
|
@ -500,7 +500,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
dataframe['buy'] = 0
|
||||
dataframe['sell'] = 0
|
||||
dataframe['buy_tag'] = None
|
||||
dataframe['sell_tag'] = None
|
||||
dataframe['exit_tag'] = None
|
||||
|
||||
# Other Defs in strategy that want to be called every loop here
|
||||
# twitter_sell = self.watch_twitter_feed(dataframe, metadata)
|
||||
@ -613,7 +613,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
sell = latest[SignalType.SELL.value] == 1
|
||||
|
||||
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',
|
||||
latest['date'], pair, str(buy), str(sell))
|
||||
@ -622,8 +622,8 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
current_time=datetime.now(timezone.utc),
|
||||
timeframe_seconds=timeframe_seconds,
|
||||
buy=buy):
|
||||
return False, sell, buy_tag, sell_tag
|
||||
return buy, sell, buy_tag, sell_tag
|
||||
return False, sell, buy_tag, exit_tag
|
||||
return buy, sell, buy_tag, exit_tag
|
||||
|
||||
def ignore_expired_candle(self, latest_date: datetime, current_time: datetime,
|
||||
timeframe_seconds: int, buy: bool):
|
||||
|
Loading…
Reference in New Issue
Block a user