diff --git a/python/valuecell/agents/auto_trading_agent/agent.py b/python/valuecell/agents/auto_trading_agent/agent.py
index 79663dba1..aa46892e4 100644
--- a/python/valuecell/agents/auto_trading_agent/agent.py
+++ b/python/valuecell/agents/auto_trading_agent/agent.py
@@ -267,7 +267,7 @@ async def _process_trading_instance(
)
trade_message = FilteredCardPushNotificationComponentData(
title=f"{config.agent_model} Trade",
- data=f"š° **Trade Executed:**\n{trade_message_text}\n",
+ data=f"š° **Trade Executed**\n\n{trade_message_text}\n",
filters=[config.agent_model],
table_title="Trade Detail",
create_time=datetime.now(timezone.utc).strftime(
@@ -548,39 +548,33 @@ def _get_instance_status_component_data(
output = []
# Header
- output.append(f"š **Trading Portfolio Status** - {instance_id}")
- output.append("\n**Instance Configuration**")
+ output.append("š **Instance Configuration**\n")
output.append(f"- Model: `{config.agent_model}`")
output.append(f"- Symbols: {', '.join(config.crypto_symbols)}")
output.append(
- f"- Status: {'š¢ Active' if instance['active'] else 'š“ Stopped'}"
+ f"- Status: {'š¢ Active' if instance['active'] else 'š“ Stopped'}\n"
)
# Portfolio Summary Section
- output.append("\nš° **Portfolio Summary**")
- output.append("\n**Overall Performance**")
- output.append(f"- Initial Capital: `${config.initial_capital:,.2f}`")
- output.append(f"- Current Value: `${portfolio_value:,.2f}`")
+ output.append("š° **Portfolio Summary**\n")
+ output.append("**Overall Performance**\n")
+ output.append(f"- Current Value: `${portfolio_value:,.2f}`\n")
pnl_emoji = "š¢" if total_pnl >= 0 else "š“"
pnl_sign = "+" if total_pnl >= 0 else ""
output.append(
- f"- Total P&L: {pnl_emoji} **{pnl_sign}${total_pnl:,.2f}** ({pnl_sign}{pnl_pct:.2f}%)"
+ f"- Total P&L: {pnl_emoji} **{pnl_sign}${total_pnl:,.2f}** ({pnl_sign}{pnl_pct:.2f}%)\n"
)
-
- output.append("\n**Cash Position**")
- output.append(f"- Available Cash: `${available_cash:,.2f}`")
+ output.append(f"- Available Cash: `${available_cash:,.2f}`\n")
# Current Positions Section
- output.append(f"\nš **Current Positions ({len(executor.positions)})**")
+ output.append(f"š **Current Positions ({len(executor.positions)})**")
if executor.positions:
output.append(
- "\n| Symbol | Type | Quantity | Avg Price | Current Price | Position Value | Unrealized P&L |"
- )
- output.append(
- "|--------|------|----------|-----------|---------------|----------------|----------------|"
+ "\n| Symbol | Type | **Position**/Quantity | **Current**/Avg | **P&L** |"
)
+ output.append("|--------|------|---------|--------|--------|")
for symbol, pos in executor.positions.items():
try:
@@ -603,15 +597,14 @@ def _get_instance_status_component_data(
)
position_value = pos.notional + unrealized_pnl
- # Format row
- pnl_emoji = "š¢" if unrealized_pnl >= 0 else "š“"
+ # Format row with merged columns
pnl_sign = "+" if unrealized_pnl >= 0 else ""
output.append(
f"| **{symbol}** | {pos.trade_type.value.upper()} | "
- f"{abs(pos.quantity):.4f} | ${pos.entry_price:,.2f} | "
- f"${current_price:,.2f} | ${position_value:,.2f} | "
- f"{pnl_emoji} {pnl_sign}${unrealized_pnl:,.2f} |"
+ f"**${position_value:,.2f}**
{abs(pos.quantity):.4f} | "
+ f"**${current_price:,.2f}**
${pos.entry_price:,.2f} | "
+ f"**{pnl_sign}${unrealized_pnl:,.2f}** |"
)
except Exception as e:
@@ -619,8 +612,9 @@ def _get_instance_status_component_data(
# Fallback display with entry price only
output.append(
f"| **{symbol}** | {pos.trade_type.value.upper()} | "
- f"{abs(pos.quantity):.4f} | ${pos.entry_price:,.2f} | "
- f"N/A | ${pos.notional:,.2f} | N/A |"
+ f"**${pos.notional:,.2f}**
{abs(pos.quantity):.4f} | "
+ f"**${pos.entry_price:,.2f}**
${pos.entry_price:,.2f} | "
+ f"**N/A** |"
)
else:
output.append("\n*No open positions*")
diff --git a/python/valuecell/agents/auto_trading_agent/formatters.py b/python/valuecell/agents/auto_trading_agent/formatters.py
index 9e2e9a8eb..12cbed843 100644
--- a/python/valuecell/agents/auto_trading_agent/formatters.py
+++ b/python/valuecell/agents/auto_trading_agent/formatters.py
@@ -5,6 +5,7 @@
from datetime import datetime, timezone
from typing import Any, Dict, Optional
+from ...utils.i18n_utils import convert_timezone, get_current_timezone
from .models import Position, TechnicalIndicators, TradeAction, TradeType
logger = logging.getLogger(__name__)
@@ -13,6 +14,63 @@
class MessageFormatter:
"""Formats various messages and notifications"""
+ @staticmethod
+ def _convert_and_format_timestamp(
+ dt: datetime, format_str: str = "%m/%d, %I:%M %p", include_tz: bool = False
+ ) -> str:
+ """
+ Convert timestamp to user's timezone and format it.
+
+ Args:
+ dt: DateTime to convert and format
+ format_str: Format string for strftime
+ include_tz: Whether to include timezone abbreviation
+
+ Returns:
+ Formatted timestamp string
+ """
+ try:
+ # Get user's configured timezone
+ user_tz = get_current_timezone()
+
+ # Convert from UTC to user's timezone
+ # Assume input is UTC if no timezone info
+ if dt.tzinfo is None:
+ dt = datetime(
+ dt.year,
+ dt.month,
+ dt.day,
+ dt.hour,
+ dt.minute,
+ dt.second,
+ tzinfo=timezone.utc,
+ )
+
+ converted_dt = convert_timezone(dt, "UTC", user_tz)
+
+ # Format the datetime
+ formatted = converted_dt.strftime(format_str)
+
+ # Optionally append timezone info
+ if include_tz:
+ formatted += f" ({user_tz})"
+
+ return formatted
+ except Exception as e:
+ logger.warning(f"Failed to convert timezone: {e}, using UTC")
+ # Fallback to UTC format
+ if dt.tzinfo is None:
+ dt = datetime(
+ dt.year,
+ dt.month,
+ dt.day,
+ dt.hour,
+ dt.minute,
+ dt.second,
+ tzinfo=timezone.utc,
+ )
+ return dt.strftime(format_str)
+
@staticmethod
def format_trade_notification(
trade_details: Dict[str, Any], agent_name: str = "AutoTrading"
@@ -33,13 +91,16 @@ def format_trade_notification(
trade_type = trade_details["trade_type"]
timestamp = trade_details["timestamp"]
+ # Convert timestamp to user's timezone
+ formatted_time = MessageFormatter._convert_and_format_timestamp(timestamp)
+
if action == "opened":
message = (
- f"{agent_name} opened a {trade_type} position on {symbol}!\n"
- f"{timestamp.strftime('%m/%d, %I:%M %p')}\n"
- f"Price: ${trade_details['entry_price']:,.2f}\n"
- f"Quantity: {trade_details['quantity']:.4f}\n"
- f"Notional: ${trade_details['notional']:,.2f}"
+ f"**{agent_name}** opened a **{trade_type}** position on **{symbol}**!\n\n"
+ f"š
{formatted_time}\n\n"
+ f"**Price:** `${trade_details['entry_price']:,.2f}`\n\n"
+ f"**Quantity:** `{trade_details['quantity']:.4f}`\n\n"
+ f"**Notional:** `${trade_details['notional']:,.2f}`"
)
else: # closed
hours = int(trade_details["holding_time"].total_seconds() // 3600)
@@ -48,15 +109,16 @@ def format_trade_notification(
)
pnl = trade_details["pnl"]
pnl_sign = "+" if pnl >= 0 else ""
+ pnl_emoji = "š¢" if pnl >= 0 else "š“"
message = (
- f"{agent_name} completed a {trade_type} trade on {symbol}!\n"
- f"{timestamp.strftime('%m/%d, %I:%M %p')}\n"
- f"Price: ${trade_details['entry_price']:,.2f} ā ${trade_details['exit_price']:,.2f}\n"
- f"Quantity: {trade_details['quantity']:.4f}\n"
- f"Notional: ${trade_details['entry_notional']:,.2f} ā ${trade_details['exit_notional']:,.2f}\n"
- f"Holding time: {hours}H {minutes}M\n"
- f"Net P&L: {pnl_sign}${pnl:,.2f}"
+ f"**{agent_name}** completed a **{trade_type}** trade on **{symbol}**!\n\n"
+ f"š
{formatted_time}\n\n"
+ f"**Price:** `${trade_details['entry_price']:,.2f}` ā `${trade_details['exit_price']:,.2f}`\n\n"
+ f"**Quantity:** `{trade_details['quantity']:.4f}`\n\n"
+ f"**Notional:** `${trade_details['entry_notional']:,.2f}` ā `${trade_details['exit_notional']:,.2f}`\n\n"
+ f"**Holding time:** `{hours}H {minutes}M`\n\n"
+ f"**Net P&L:** {pnl_emoji} **{pnl_sign}${pnl:,.2f}**"
)
return message
@@ -106,9 +168,14 @@ def format_portfolio_notification(
"data": {"Portfolio": portfolio_value},
}
+ # Convert timestamp to user's timezone for display
+ formatted_time = MessageFormatter._convert_and_format_timestamp(
+ timestamp, format_str="%m/%d, %I:%M %p", include_tz=True
+ )
+
display_message = (
f"š° Portfolio Update\n"
- f"Time: {timestamp.strftime('%m/%d, %I:%M %p UTC')}\n"
+ f"Time: {formatted_time}\n"
f"Total Value: ${portfolio_value:,.2f}\n"
f"Open Positions: {positions_count}\n"
f"Available Capital: ${current_capital:,.2f}"
@@ -146,6 +213,11 @@ def format_market_analysis_notification(
try:
timestamp = datetime.now(timezone.utc)
+ # Convert timestamp to user's timezone for display
+ formatted_time = MessageFormatter._convert_and_format_timestamp(
+ timestamp, format_str="%m/%d, %I:%M %p", include_tz=True
+ )
+
# Format action with emoji
action_emoji = {
TradeAction.BUY: "š¢",
@@ -155,7 +227,7 @@ def format_market_analysis_notification(
message = (
f"š **Market Analysis - {symbol}**\n"
- f"Time: {timestamp.strftime('%m/%d, %I:%M %p UTC')}\n\n"
+ f"Time: {formatted_time}\n\n"
f"**Current Price:** ${indicators.close_price:,.2f}\n"
f"**Decision:** {action_emoji.get(action, '')} {action.value.upper()}"
)