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',
|
'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:
|
||||||
|
@ -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"
|
||||||
|
@ -201,11 +201,11 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
if len(open_trades) != 0:
|
if len(open_trades) != 0:
|
||||||
msg = {
|
msg = {
|
||||||
'type': RPCMessageType.WARNING,
|
'type': RPCMessageType.WARNING,
|
||||||
'status': f"{len(open_trades)} open trades active.\n\n"
|
'status': f"{len(open_trades)} open trades active.\n\n"
|
||||||
f"Handle these trades manually on {self.exchange.name}, "
|
f"Handle these trades manually on {self.exchange.name}, "
|
||||||
f"or '/start' the bot again and use '/stopbuy' "
|
f"or '/start' the bot again and use '/stopbuy' "
|
||||||
f"to handle open trades gracefully. \n"
|
f"to handle open trades gracefully. \n"
|
||||||
f"{'Trades are simulated.' if self.config['dry_run'] else ''}",
|
f"{'Trades are simulated.' if self.config['dry_run'] else ''}",
|
||||||
}
|
}
|
||||||
self.rpc.send_msg(msg)
|
self.rpc.send_msg(msg)
|
||||||
|
|
||||||
@ -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'],
|
||||||
|
@ -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)
|
||||||
@ -389,7 +392,7 @@ class Backtesting:
|
|||||||
detail_data = detail_data.loc[
|
detail_data = detail_data.loc[
|
||||||
(detail_data['date'] >= sell_candle_time) &
|
(detail_data['date'] >= sell_candle_time) &
|
||||||
(detail_data['date'] < sell_candle_end)
|
(detail_data['date'] < sell_candle_end)
|
||||||
].copy()
|
].copy()
|
||||||
if len(detail_data) == 0:
|
if len(detail_data) == 0:
|
||||||
# Fall back to "regular" data if no detail data was found for this candle
|
# Fall back to "regular" data if no detail data was found for this candle
|
||||||
return self._get_sell_trade_entry_for_candle(trade, sell_row)
|
return self._get_sell_trade_entry_for_candle(trade, sell_row)
|
||||||
@ -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
|
||||||
|
@ -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,12 +385,10 @@ 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)
|
exit_tag_results = generate_tag_metrics("exit_tag", starting_balance=starting_balance,
|
||||||
sell_tag_results = generate_tag_metrics("sell_tag",btdata, stake_currency=stake_currency,
|
results=results, skip_nan=False)
|
||||||
starting_balance=starting_balance,
|
|
||||||
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)
|
||||||
@ -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,30 +584,38 @@ 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
|
||||||
:param pair_results: List of Dictionaries - one entry per pair + final TOTAL row
|
:param pair_results: List of Dictionaries - one entry per pair + final TOTAL row
|
||||||
: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 == "buy_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)
|
||||||
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()
|
||||||
|
|
||||||
|
|
||||||
|
@ -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!
|
||||||
|
@ -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):
|
||||||
@ -873,28 +874,28 @@ class Trade(_DECL_BASE, LocalTrade):
|
|||||||
|
|
||||||
if(pair is not None):
|
if(pair is not None):
|
||||||
tag_perf = Trade.query.with_entities(
|
tag_perf = Trade.query.with_entities(
|
||||||
Trade.buy_tag,
|
Trade.buy_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.buy_tag,
|
Trade.buy_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.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.buy_tag,
|
Trade.id,
|
||||||
Trade.sell_tag,
|
Trade.buy_tag,
|
||||||
func.sum(Trade.close_profit).label('profit_sum'),
|
Trade.exit_tag,
|
||||||
func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
|
func.sum(Trade.close_profit).label('profit_sum'),
|
||||||
func.count(Trade.pair).label('count')
|
func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
|
||||||
).filter(Trade.is_open.is_(False))\
|
func.count(Trade.pair).label('count')
|
||||||
.filter(Trade.pair.lower() == pair.lower()) \
|
).filter(Trade.is_open.is_(False))\
|
||||||
.order_by(desc('profit_sum_abs')) \
|
.filter(Trade.pair == pair) \
|
||||||
.all()
|
.order_by(desc('profit_sum_abs')) \
|
||||||
|
.all()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
tag_perf = Trade.query.with_entities(
|
tag_perf = Trade.query.with_entities(
|
||||||
Trade.buy_tag,
|
Trade.id,
|
||||||
Trade.sell_tag,
|
Trade.buy_tag,
|
||||||
func.sum(Trade.close_profit).label('profit_sum'),
|
Trade.exit_tag,
|
||||||
func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
|
func.sum(Trade.close_profit).label('profit_sum'),
|
||||||
func.count(Trade.pair).label('count')
|
func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
|
||||||
).filter(Trade.is_open.is_(False))\
|
func.count(Trade.pair).label('count')
|
||||||
.group_by(Trade.pair) \
|
).filter(Trade.is_open.is_(False))\
|
||||||
.order_by(desc('profit_sum_abs')) \
|
.group_by(Trade.id) \
|
||||||
.all()
|
.order_by(desc('profit_sum_abs')) \
|
||||||
|
.all()
|
||||||
|
|
||||||
return [
|
return_list = []
|
||||||
{ 'mix_tag': str(buy_tag) + " " +str(sell_tag),
|
for id, buy_tag, exit_tag, profit, profit_abs, count in tag_perf:
|
||||||
'profit': profit,
|
buy_tag = buy_tag if buy_tag is not None else "Other"
|
||||||
'profit_abs': profit_abs,
|
exit_tag = exit_tag if exit_tag is not None else "Other"
|
||||||
'count': count
|
|
||||||
}
|
if(exit_tag is not None and buy_tag is not None):
|
||||||
for buy_tag, sell_tag, profit, profit_abs, count in tag_perf
|
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})
|
||||||
|
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
|
@staticmethod
|
||||||
def get_best_pair(start_date: datetime = datetime.fromtimestamp(0)):
|
def get_best_pair(start_date: datetime = datetime.fromtimestamp(0)):
|
||||||
|
@ -105,7 +105,7 @@ class RPC:
|
|||||||
val = {
|
val = {
|
||||||
'dry_run': config['dry_run'],
|
'dry_run': config['dry_run'],
|
||||||
'stake_currency': config['stake_currency'],
|
'stake_currency': config['stake_currency'],
|
||||||
'stake_currency_decimals': decimals_per_coin(config['stake_currency']),
|
'stake_currency_decimals': decimals_per_coin(config['stake_currency']),
|
||||||
'stake_amount': config['stake_amount'],
|
'stake_amount': config['stake_amount'],
|
||||||
'available_capital': config.get('available_capital'),
|
'available_capital': config.get('available_capital'),
|
||||||
'max_open_trades': (config['max_open_trades']
|
'max_open_trades': (config['max_open_trades']
|
||||||
@ -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]]:
|
||||||
"""
|
"""
|
||||||
|
@ -108,7 +108,7 @@ class Telegram(RPCHandler):
|
|||||||
r'/trades$', r'/performance$', r'/daily$', r'/daily \d+$',
|
r'/trades$', r'/performance$', r'/daily$', r'/daily \d+$',
|
||||||
r'/profit$', r'/profit \d+',
|
r'/profit$', r'/profit \d+',
|
||||||
r'/stats$', r'/count$', r'/locks$', r'/balance$',
|
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'/stopbuy$', r'/reload_config$', r'/show_config$',
|
||||||
r'/logs$', r'/whitelist$', r'/blacklist$', r'/edge$',
|
r'/logs$', r'/whitelist$', r'/blacklist$', r'/edge$',
|
||||||
r'/forcebuy$', r'/help$', r'/version$']
|
r'/forcebuy$', r'/help$', r'/version$']
|
||||||
@ -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}`",
|
||||||
@ -923,12 +897,12 @@ class Telegram(RPCHandler):
|
|||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
pair=None
|
pair = None
|
||||||
if context.args:
|
if context.args:
|
||||||
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
|
||||||
@ -958,15 +932,15 @@ class Telegram(RPCHandler):
|
|||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
pair=None
|
pair = None
|
||||||
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))
|
||||||
@ -993,13 +967,14 @@ class Telegram(RPCHandler):
|
|||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
pair=None
|
pair = None
|
||||||
if context.args:
|
if context.args:
|
||||||
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'])} "
|
||||||
|
@ -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):
|
||||||
|
Loading…
Reference in New Issue
Block a user