ソースを参照

Refactor positions command to enhance data retrieval and formatting. Improved handling of position attributes, including entry price, current amount, and duration calculations. Updated display logic for position details, ensuring accurate representation of financial metrics such as unrealized P&L, ROE, and margin used. Enhanced error handling for date parsing and added support for displaying stop loss and take profit prices.

Carles Sentis 1 週間 前
コミット
45bdcf0d60
2 ファイル変更56 行追加47 行削除
  1. 55 46
      src/commands/info/positions.py
  2. 1 1
      trading_bot.py

+ 55 - 46
src/commands/info/positions.py

@@ -41,24 +41,29 @@ class PositionsCommands(InfoCommandsBase):
             
             for position_trade in open_positions:
                 try:
+                    # Get position data with defaults
                     symbol = position_trade['symbol']
-                    base_asset = symbol.split('/')[0] if '/' in symbol else symbol.split(':')[0]
+                    base_asset = symbol.split('/')[0] if '/' in symbol else symbol
                     position_side = position_trade.get('position_side', 'unknown')
-                    abs_current_amount = abs(float(position_trade.get('current_position_size', 0)))
-                    entry_price = float(position_trade.get('entry_price', 0))
-                    mark_price = position_trade.get('mark_price', entry_price)
-                    trade_type = position_trade.get('trade_type', 'unknown')
-
-                    # Calculate duration
-                    duration_str = "N/A"
+                    entry_price = float(position_trade.get('entry_price', 0.0))
+                    current_amount = float(position_trade.get('current_position_size', 0.0))
+                    abs_current_amount = abs(current_amount)
+                    trade_type = position_trade.get('trade_type', 'manual')
+                    
+                    # Calculate position duration
                     position_opened_at_str = position_trade.get('position_opened_at')
+                    duration_str = "N/A"
                     if position_opened_at_str:
                         try:
-                            position_opened_at = datetime.fromisoformat(position_opened_at_str.replace('Z', '+00:00'))
-                            duration = datetime.now(timezone.utc) - position_opened_at
+                            opened_at_dt = datetime.fromisoformat(position_opened_at_str)
+                            if opened_at_dt.tzinfo is None:
+                                opened_at_dt = opened_at_dt.replace(tzinfo=timezone.utc)
+                            now_utc = datetime.now(timezone.utc)
+                            duration = now_utc - opened_at_dt
+                            
                             days = duration.days
-                            hours = duration.seconds // 3600
-                            minutes = (duration.seconds % 3600) // 60
+                            hours, remainder = divmod(duration.seconds, 3600)
+                            minutes, _ = divmod(remainder, 60)
                             
                             parts = []
                             if days > 0:
@@ -71,41 +76,32 @@ class PositionsCommands(InfoCommandsBase):
                         except ValueError:
                             logger.warning(f"Could not parse position_opened_at: {position_opened_at_str} for {symbol}")
                             duration_str = "Error"
-
+                    
+                    # Get price data with defaults
+                    mark_price = float(position_trade.get('mark_price', entry_price))
+                    
                     # Calculate unrealized PnL
-                    unrealized_pnl = position_trade.get('unrealized_pnl', 0.0)
+                    unrealized_pnl = float(position_trade.get('unrealized_pnl', 0.0))
                     
                     # Get ROE from database
-                    roe_percentage = position_trade.get('roe_percentage', 0.0)
+                    roe_percentage = float(position_trade.get('roe_percentage', 0.0))
 
                     # Add to totals
-                    individual_position_value = position_trade.get('position_value', 0.0)
-                    if individual_position_value is None or individual_position_value == 0:
+                    individual_position_value = float(position_trade.get('position_value', 0.0))
+                    if individual_position_value <= 0:
                         individual_position_value = abs_current_amount * mark_price
                     
                     total_position_value += individual_position_value
                     total_unrealized += unrealized_pnl
                     
                     # Add margin to total
-                    margin_used = position_trade.get('margin_used', 0.0)
+                    margin_used = float(position_trade.get('margin_used', 0.0))
                     if margin_used > 0:
                         total_margin_used += margin_used
-
-                    # Format position details
-                    formatter = self._get_formatter()
-                    entry_price_str = formatter.format_price_with_symbol(entry_price, base_asset)
-                    mark_price_str = formatter.format_price_with_symbol(mark_price, base_asset)
-                    size_str = formatter.format_amount(abs_current_amount, base_asset)
-
-                    # Position header
-                    pos_emoji = ""
-                    direction_text = ""
-                    if position_side == 'long':
-                        pos_emoji = "🟢"
-                        direction_text = "LONG"
-                    else:  # Short position
-                        pos_emoji = "🔴"
-                        direction_text = "SHORT"
+                    
+                    # --- Position Header Formatting (Emoji, Direction, Leverage) ---
+                    pos_emoji = "🟢" if position_side == 'long' else "🔴"
+                    direction_text = position_side.upper()
                     
                     leverage = position_trade.get('leverage')
                     if leverage is not None:
@@ -116,44 +112,57 @@ class PositionsCommands(InfoCommandsBase):
                         except ValueError:
                             logger.warning(f"Could not parse leverage value: {leverage} for {symbol}")
 
-                    # Add type indicator
+                    # --- Format Output String ---
+                    formatter = self._get_formatter()
+
+                    # Get price precisions
+                    entry_price_str = formatter.format_price_with_symbol(entry_price, base_asset)
+                    mark_price_str = formatter.format_price_with_symbol(mark_price, base_asset)
+
+                    # Get amount precision for position size
+                    size_str = formatter.format_amount(abs_current_amount, base_asset)
+
                     type_indicator = ""
                     if position_trade.get('trade_lifecycle_id'):
                         type_indicator = " 🤖"
                     elif trade_type == 'external':
                         type_indicator = " 🔄"
-
-                    # Build position text
+                    
                     positions_text += f"{pos_emoji} <b>{base_asset} ({direction_text}){type_indicator}</b>\n"
                     positions_text += f"   📏 Size: {size_str} {base_asset}\n"
                     positions_text += f"   💰 Entry: {entry_price_str}\n"
                     positions_text += f"   ⏳ Duration: {duration_str}\n"
+                    
+                    # Display individual position value
                     positions_text += f"   🏦 Value: ${individual_position_value:,.2f}\n"
+                    
                     if mark_price > 0 and abs(mark_price - entry_price) > 1e-9:
                         positions_text += f"   📈 Mark: {mark_price_str}\n"
+                    
                     pnl_emoji = "🟢" if unrealized_pnl >= 0 else "🔴"
                     positions_text += f"   {pnl_emoji} P&L: ${unrealized_pnl:,.2f}\n"
+                    
+                    # Show ROE
                     roe_emoji = "🟢" if roe_percentage >= 0 else "🔴"
                     positions_text += f"   {roe_emoji} ROE: {roe_percentage:+.2f}%\n"
-
-                    # Add risk management info
-                    if margin_used is not None:
+                    
+                    # Show exchange-provided risk data if available
+                    if margin_used > 0:
                         positions_text += f"   💳 Margin Used: ${margin_used:,.2f}\n"
                     if position_trade.get('liquidation_price') is not None and position_trade.get('liquidation_price') > 0:
                         liq_price_str = formatter.format_price_with_symbol(position_trade.get('liquidation_price'), base_asset)
                         positions_text += f"   ⚠️ Liquidation: {liq_price_str}\n"
+                    
+                    # Show stop loss if linked in database
                     if position_trade.get('stop_loss_price'):
                         sl_price = position_trade['stop_loss_price']
                         positions_text += f"   🛑 Stop Loss: {formatter.format_price_with_symbol(sl_price, base_asset)}\n"
+                    
+                    # Show take profit if linked in database
                     if position_trade.get('take_profit_price'):
                         tp_price = position_trade['take_profit_price']
                         positions_text += f"   🎯 Take Profit: {formatter.format_price_with_symbol(tp_price, base_asset)}\n"
-
-                    # Add external stop losses
-                    external_sls = self._get_external_stop_losses(symbol, position_side, entry_price, abs_current_amount, exchange_orders)
-                    for ext_sl_price in external_sls:
-                        positions_text += f"   🛡️ External SL: {formatter.format_price_with_symbol(ext_sl_price, base_asset)}\n"
-
+                    
                     positions_text += f"   🆔 Lifecycle ID: {position_trade['trade_lifecycle_id'][:8]}\n\n"
 
                 except Exception as e:

+ 1 - 1
trading_bot.py

@@ -14,7 +14,7 @@ from datetime import datetime
 from pathlib import Path
 
 # Bot version
-BOT_VERSION = "2.4.194"
+BOT_VERSION = "2.4.195"
 
 # Add src directory to Python path
 sys.path.insert(0, str(Path(__file__).parent / "src"))