Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 18 additions & 24 deletions python/valuecell/agents/auto_trading_agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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:
Expand All @@ -603,24 +597,24 @@ 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}** <br> {abs(pos.quantity):.4f} | "
f"**${current_price:,.2f}** <br> ${pos.entry_price:,.2f} | "
f"**{pnl_sign}${unrealized_pnl:,.2f}** |"
)

except Exception as e:
logger.warning(f"Failed to get price for {symbol}: {e}")
# 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}** <br> {abs(pos.quantity):.4f} | "
f"**${pos.entry_price:,.2f}** <br> ${pos.entry_price:,.2f} | "
f"**N/A** |"
)
else:
output.append("\n*No open positions*")
Expand Down
100 changes: 86 additions & 14 deletions python/valuecell/agents/auto_trading_agent/formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand All @@ -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"
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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}"
Expand Down Expand Up @@ -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: "🟢",
Expand All @@ -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()}"
)
Expand Down