|
@@ -402,13 +402,21 @@ class TradingStats:
|
|
|
SUM(total_realized_pnl) as total_pnl_from_cycles,
|
|
|
SUM(total_completed_cycles) as total_completed_cycles_sum,
|
|
|
MIN(first_cycle_closed_at) as overall_first_cycle_closed,
|
|
|
- MAX(last_cycle_closed_at) as overall_last_cycle_closed
|
|
|
+ MAX(last_cycle_closed_at) as overall_last_cycle_closed,
|
|
|
+ SUM(total_entry_volume) as total_entry_volume_sum,
|
|
|
+ MAX(best_roe_percentage) as overall_best_roe,
|
|
|
+ MIN(worst_roe_percentage) as overall_worst_roe
|
|
|
FROM token_stats
|
|
|
"""
|
|
|
token_stats_summary = self.db_manager._fetchone_query(query_token_stats_summary)
|
|
|
|
|
|
+ # Find the specific trades for best/worst ROE
|
|
|
+ best_roe_trade = self.db_manager._fetchone_query("SELECT token, best_roe_pnl, best_roe_percentage FROM token_stats ORDER BY best_roe_percentage DESC LIMIT 1")
|
|
|
+ worst_roe_trade = self.db_manager._fetchone_query("SELECT token, worst_roe_pnl, worst_roe_percentage FROM token_stats ORDER BY worst_roe_percentage ASC LIMIT 1")
|
|
|
+
|
|
|
total_pnl_from_cycles = token_stats_summary['total_pnl_from_cycles'] if token_stats_summary and token_stats_summary['total_pnl_from_cycles'] is not None else 0.0
|
|
|
total_completed_cycles_sum = token_stats_summary['total_completed_cycles_sum'] if token_stats_summary and token_stats_summary['total_completed_cycles_sum'] is not None else 0
|
|
|
+ total_entry_volume_sum = token_stats_summary['total_entry_volume_sum'] if token_stats_summary and token_stats_summary['total_entry_volume_sum'] is not None else 0.0
|
|
|
|
|
|
# Total trades considered as sum of completed cycles and currently open positions
|
|
|
total_trades_redefined = total_completed_cycles_sum + open_positions_count
|
|
@@ -455,15 +463,23 @@ class TradingStats:
|
|
|
if not last_activity_ts or last_open_trade_ts > last_activity_ts:
|
|
|
last_activity_ts = last_open_trade_ts
|
|
|
|
|
|
+ # Get drawdown info
|
|
|
+ _, max_drawdown_pct, drawdown_start_date = self.performance_calculator.get_live_max_drawdown()
|
|
|
+
|
|
|
return {
|
|
|
'total_trades': total_trades_redefined,
|
|
|
'completed_trades': total_completed_cycles_sum,
|
|
|
'initial_balance': initial_balance,
|
|
|
'total_pnl': total_pnl_from_cycles,
|
|
|
+ 'total_volume': total_entry_volume_sum,
|
|
|
'days_active': days_active,
|
|
|
'start_date': start_date_obj.strftime('%Y-%m-%d'),
|
|
|
'last_trade': last_activity_ts,
|
|
|
- 'open_positions_count': open_positions_count
|
|
|
+ 'open_positions_count': open_positions_count,
|
|
|
+ 'best_roe_trade': best_roe_trade,
|
|
|
+ 'worst_roe_trade': worst_roe_trade,
|
|
|
+ 'max_drawdown_percentage': max_drawdown_pct,
|
|
|
+ 'drawdown_start_date': drawdown_start_date
|
|
|
}
|
|
|
|
|
|
def _get_open_positions_count_from_db(self) -> int:
|
|
@@ -507,7 +523,12 @@ class TradingStats:
|
|
|
'completed_exit_volume': 0.0,
|
|
|
'total_cancelled': 0,
|
|
|
'total_duration_seconds': 0,
|
|
|
- 'avg_trade_duration': "N/A"
|
|
|
+ 'avg_trade_duration': "N/A",
|
|
|
+ 'avg_roe': 0.0,
|
|
|
+ 'best_roe_pnl': 0.0,
|
|
|
+ 'best_roe_percentage': 0.0,
|
|
|
+ 'worst_roe_pnl': 0.0,
|
|
|
+ 'worst_roe_percentage': 0.0
|
|
|
}
|
|
|
|
|
|
if token_stats:
|
|
@@ -525,7 +546,8 @@ class TradingStats:
|
|
|
|
|
|
largest_win_percentage = (largest_win_pnl / largest_win_entry_volume * 100) if largest_win_entry_volume > 0 else 0.0
|
|
|
largest_loss_percentage = (largest_loss_pnl / largest_loss_entry_volume * 100) if largest_loss_entry_volume > 0 else 0.0
|
|
|
-
|
|
|
+ avg_roe = (token_stats.get('total_roe_percentage', 0.0) / total_cycles) if total_cycles > 0 else 0.0
|
|
|
+
|
|
|
perf_stats.update({
|
|
|
'completed_trades': total_cycles,
|
|
|
'total_pnl': token_stats.get('total_realized_pnl', 0.0),
|
|
@@ -537,6 +559,11 @@ class TradingStats:
|
|
|
'largest_loss': largest_loss_pnl,
|
|
|
'largest_win_percentage': largest_win_percentage,
|
|
|
'largest_loss_percentage': largest_loss_percentage,
|
|
|
+ 'avg_roe': avg_roe,
|
|
|
+ 'best_roe_pnl': token_stats.get('best_roe_pnl', 0.0),
|
|
|
+ 'best_roe_percentage': token_stats.get('best_roe_percentage', 0.0),
|
|
|
+ 'worst_roe_pnl': token_stats.get('worst_roe_pnl', 0.0),
|
|
|
+ 'worst_roe_percentage': token_stats.get('worst_roe_percentage', 0.0),
|
|
|
'total_wins': winning_cycles,
|
|
|
'total_losses': losing_cycles,
|
|
|
'completed_entry_volume': token_stats.get('total_entry_volume', 0.0),
|
|
@@ -635,6 +662,18 @@ class TradingStats:
|
|
|
f"• {pnl_emoji} Total P&L: {await formatter.format_price_with_symbol(total_pnl_val)} ({total_return_pct:+.2f}%)"
|
|
|
]
|
|
|
|
|
|
+ # Drawdown
|
|
|
+ max_drawdown_pct = basic_stats.get('max_drawdown_percentage', 0.0)
|
|
|
+ drawdown_start_date_iso = basic_stats.get('drawdown_start_date')
|
|
|
+ drawdown_date_str = ""
|
|
|
+ if drawdown_start_date_iso:
|
|
|
+ try:
|
|
|
+ drawdown_date = datetime.fromisoformat(drawdown_start_date_iso).strftime('%Y-%m-%d')
|
|
|
+ drawdown_date_str = f" (since {drawdown_date})"
|
|
|
+ except (ValueError, TypeError):
|
|
|
+ pass
|
|
|
+ stats_text_parts.append(f"• Max Drawdown: {max_drawdown_pct:.2f}%{drawdown_date_str}")
|
|
|
+
|
|
|
# Performance Metrics
|
|
|
perf = basic_stats.get('performance_metrics', {})
|
|
|
if perf:
|
|
@@ -645,27 +684,31 @@ class TradingStats:
|
|
|
stats_text_parts.append(f"• Win Rate: {perf.get('win_rate', 0.0):.2f}% ({perf.get('num_wins', 0)} wins)")
|
|
|
stats_text_parts.append(f"• Profit Factor: {perf.get('profit_factor', 0.0):.2f}")
|
|
|
|
|
|
- # Largest Trades
|
|
|
+ # Largest Trades by P&L
|
|
|
if perf.get('largest_win') is not None:
|
|
|
largest_win_pct_str = f" ({perf.get('largest_win_entry_pct', 0):.2f}%)" if perf.get('largest_win_entry_pct') is not None else ""
|
|
|
largest_win_token = perf.get('largest_win_token', 'N/A')
|
|
|
- stats_text_parts.append(f"• Largest Winning Trade: {await formatter.format_price_with_symbol(perf['largest_win'])}{largest_win_pct_str} ({largest_win_token})")
|
|
|
+ stats_text_parts.append(f"• Largest Win (P&L): {await formatter.format_price_with_symbol(perf['largest_win'])}{largest_win_pct_str} ({largest_win_token})")
|
|
|
if perf.get('largest_loss') is not None:
|
|
|
largest_loss_pct_str = f" ({perf.get('largest_loss_entry_pct', 0):.2f}%)" if perf.get('largest_loss_entry_pct') is not None else ""
|
|
|
largest_loss_token = perf.get('largest_loss_token', 'N/A')
|
|
|
- stats_text_parts.append(f"• Largest Losing Trade: {await formatter.format_price_with_symbol(-perf['largest_loss'])}{largest_loss_pct_str} ({largest_loss_token})")
|
|
|
-
|
|
|
- # ROE-based metrics if available
|
|
|
- largest_win_roe = perf.get('largest_win_roe')
|
|
|
- largest_loss_roe = perf.get('largest_loss_roe')
|
|
|
- if largest_win_roe is not None:
|
|
|
- largest_win_roe_pnl = perf.get('largest_win_roe_pnl', 0.0)
|
|
|
- largest_win_roe_token = perf.get('largest_win_roe_token', 'N/A')
|
|
|
- stats_text_parts.append(f"• Best ROE Trade: {await formatter.format_price_with_symbol(largest_win_roe_pnl)} (+{largest_win_roe:.2f}%) ({largest_win_roe_token})")
|
|
|
- if largest_loss_roe is not None:
|
|
|
- largest_loss_roe_pnl = perf.get('largest_loss_roe_pnl', 0.0)
|
|
|
- largest_loss_roe_token = perf.get('largest_loss_roe_token', 'N/A')
|
|
|
- stats_text_parts.append(f"• Worst ROE Trade: {await formatter.format_price_with_symbol(-largest_loss_roe_pnl)} (-{largest_loss_roe:.2f}%) ({largest_loss_roe_token})")
|
|
|
+ stats_text_parts.append(f"• Largest Loss (P&L): {await formatter.format_price_with_symbol(perf['largest_loss'])}{largest_loss_pct_str} ({largest_loss_token})")
|
|
|
+
|
|
|
+ # Best/Worst by ROE from basic_stats
|
|
|
+ best_roe_trade = basic_stats.get('best_roe_trade')
|
|
|
+ worst_roe_trade = basic_stats.get('worst_roe_trade')
|
|
|
+
|
|
|
+ if best_roe_trade:
|
|
|
+ best_roe_pnl = best_roe_trade.get('best_roe_pnl', 0.0)
|
|
|
+ best_roe_pct = best_roe_trade.get('best_roe_percentage', 0.0)
|
|
|
+ best_roe_token = best_roe_trade.get('token', 'N/A')
|
|
|
+ stats_text_parts.append(f"• Best Trade (ROE): {await formatter.format_price_with_symbol(best_roe_pnl)} ({best_roe_pct:+.2f}%) ({best_roe_token})")
|
|
|
+
|
|
|
+ if worst_roe_trade:
|
|
|
+ worst_roe_pnl = worst_roe_trade.get('worst_roe_pnl', 0.0)
|
|
|
+ worst_roe_pct = worst_roe_trade.get('worst_roe_percentage', 0.0)
|
|
|
+ worst_roe_token = worst_roe_trade.get('token', 'N/A')
|
|
|
+ stats_text_parts.append(f"• Worst Trade (ROE): {await formatter.format_price_with_symbol(worst_roe_pnl)} ({worst_roe_pct:+.2f}%) ({worst_roe_token})")
|
|
|
|
|
|
# Best/Worst Tokens
|
|
|
best_token_stats = basic_stats.get('best_token')
|
|
@@ -692,27 +735,30 @@ class TradingStats:
|
|
|
|
|
|
# Completed Trades Summary
|
|
|
parts.append("📈 <b>Completed Trades Summary:</b>")
|
|
|
- if token_stats.get('completed_trades', 0) > 0:
|
|
|
- pnl_emoji = "✅" if token_stats.get('total_pnl', 0) >= 0 else "🔻"
|
|
|
- entry_vol = token_stats.get('completed_entry_volume', 0.0)
|
|
|
- pnl_pct = (token_stats.get('total_pnl', 0.0) / entry_vol * 100) if entry_vol > 0 else 0.0
|
|
|
-
|
|
|
- parts.append(f"• Total Completed: {token_stats.get('completed_trades', 0)}")
|
|
|
- parts.append(f"• {pnl_emoji} Realized P&L: {await formatter.format_price_with_symbol(token_stats.get('total_pnl', 0.0))} ({pnl_pct:+.2f}%)")
|
|
|
- parts.append(f"• Win Rate: {token_stats.get('win_rate', 0.0):.1f}% ({token_stats.get('total_wins', 0)}W / {token_stats.get('total_losses', 0)}L)")
|
|
|
- parts.append(f"• Profit Factor: {token_stats.get('profit_factor', 0.0):.2f}")
|
|
|
- parts.append(f"• Expectancy: {await formatter.format_price_with_symbol(token_stats.get('expectancy', 0.0))}")
|
|
|
- parts.append(f"• Avg Win: {await formatter.format_price_with_symbol(token_stats.get('avg_win', 0.0))} | Avg Loss: {await formatter.format_price_with_symbol(token_stats.get('avg_loss', 0.0))}")
|
|
|
+ if token_stats['performance_summary'].get('completed_trades', 0) > 0:
|
|
|
+ perf = token_stats['performance_summary']
|
|
|
+ pnl_emoji = "✅" if perf.get('total_pnl', 0) >= 0 else "🔻"
|
|
|
+ entry_vol = perf.get('completed_entry_volume', 0.0)
|
|
|
+ pnl_pct = (perf.get('total_pnl', 0.0) / entry_vol * 100) if entry_vol > 0 else 0.0
|
|
|
|
|
|
- # Format largest trades with percentages
|
|
|
- largest_win_pct_str = f" ({token_stats.get('largest_win_entry_pct', 0):.2f}%)" if token_stats.get('largest_win_entry_pct') is not None else ""
|
|
|
- largest_loss_pct_str = f" ({token_stats.get('largest_loss_entry_pct', 0):.2f}%)" if token_stats.get('largest_loss_entry_pct') is not None else ""
|
|
|
+ parts.append(f"• Total Completed: {perf.get('completed_trades', 0)}")
|
|
|
+ parts.append(f"• {pnl_emoji} Realized P&L: {await formatter.format_price_with_symbol(perf.get('total_pnl', 0.0))} ({pnl_pct:+.2f}%)")
|
|
|
+ parts.append(f"• Win Rate: {perf.get('win_rate', 0.0):.1f}% ({perf.get('total_wins', 0)}W / {perf.get('total_losses', 0)}L)")
|
|
|
+ parts.append(f"• Avg ROE: {perf.get('avg_roe', 0.0):.2f}%")
|
|
|
|
|
|
- parts.append(f"• Largest Win: {await formatter.format_price_with_symbol(token_stats.get('largest_win', 0.0))}{largest_win_pct_str} | Largest Loss: {await formatter.format_price_with_symbol(token_stats.get('largest_loss', 0.0))}{largest_loss_pct_str}")
|
|
|
- parts.append(f"• Entry Volume: {await formatter.format_price_with_symbol(token_stats.get('completed_entry_volume', 0.0))}")
|
|
|
- parts.append(f"• Exit Volume: {await formatter.format_price_with_symbol(token_stats.get('completed_exit_volume', 0.0))}")
|
|
|
- parts.append(f"• Average Trade Duration: {token_stats.get('avg_trade_duration', 'N/A')}")
|
|
|
- parts.append(f"• Cancelled Cycles: {token_stats.get('total_cancelled', 0)}")
|
|
|
+ # Largest Win/Loss by P&L
|
|
|
+ largest_win_pnl_str = f"{await formatter.format_price_with_symbol(perf.get('largest_win', 0.0))} ({perf.get('largest_win_percentage', 0.0):.2f}%)"
|
|
|
+ largest_loss_pnl_str = f"{await formatter.format_price_with_symbol(perf.get('largest_loss', 0.0))} ({perf.get('largest_loss_percentage', 0.0):.2f}%)"
|
|
|
+ parts.append(f"• Largest Win (P&L): {largest_win_pnl_str}")
|
|
|
+ parts.append(f"• Largest Loss (P&L): {largest_loss_pnl_str}")
|
|
|
+
|
|
|
+ # Best/Worst by ROE
|
|
|
+ best_roe_str = f"{await formatter.format_price_with_symbol(perf.get('best_roe_pnl', 0.0))} ({perf.get('best_roe_percentage', 0.0):+.2f}%)"
|
|
|
+ worst_roe_str = f"{await formatter.format_price_with_symbol(perf.get('worst_roe_pnl', 0.0))} ({perf.get('worst_roe_percentage', 0.0):+.2f}%)"
|
|
|
+ parts.append(f"• Best Trade (ROE): {best_roe_str}")
|
|
|
+ parts.append(f"• Worst Trade (ROE): {worst_roe_str}")
|
|
|
+
|
|
|
+ parts.append(f"• Average Trade Duration: {perf.get('avg_trade_duration', 'N/A')}")
|
|
|
else:
|
|
|
parts.append("• No completed trades for this token yet.")
|
|
|
parts.append("")
|