"""
Domestic stock trading module
- Fixed amount purchase per stock
- Market price buy/sell
- Full liquidation sell
"""

import asyncio
import datetime
import logging
import math
import time
from pathlib import Path
from typing import Optional, Dict, List, Any
from zoneinfo import ZoneInfo

import yaml

# Path to directory where current file is located
TRADING_DIR = Path(__file__).parent

# kis_auth import (same directory)
import sys
sys.path.insert(0, str(TRADING_DIR))
import kis_auth as ka
from kis_auth import (
    KISAuthError,
    TokenFileError,
    CredentialMismatchError,
    TokenRequestError
)

# Logging setup
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

KST = ZoneInfo("Asia/Seoul")


def _now_kst() -> datetime.datetime:
    """Return timezone-aware current time in Korea Standard Time."""
    return datetime.datetime.now(KST)


def _domestic_order_window(now: Optional[datetime.datetime] = None) -> str:
    """Classify the Korean domestic stock order window using KST.

    Returns one of:
    - regular: 09:00~15:30 market orders
    - closing: 15:40~16:00 after-hours closing-price orders
    - reserved: KIS reserved-order window, excluding 23:40~00:10
    - unavailable: gaps where neither regular/closing nor reserved orders are accepted
    """
    now = now or _now_kst()
    current_time = now.astimezone(KST).time() if now.tzinfo else now.time()

    if datetime.time(9, 0) <= current_time <= datetime.time(15, 30):
        return "regular"
    if datetime.time(15, 40) <= current_time <= datetime.time(16, 0):
        return "closing"
    if datetime.time(16, 0) < current_time <= datetime.time(23, 40):
        return "reserved"
    if datetime.time(0, 10) <= current_time <= datetime.time(7, 30):
        return "reserved"
    return "unavailable"

# Load configuration file
CONFIG_FILE = TRADING_DIR / "config" / "kis_devlp.yaml"
with open(CONFIG_FILE, encoding="UTF-8") as f:
    _cfg = yaml.safe_load(f)


def _resolve_sell_quantity(holding_quantity: int, quantity: int = None) -> int:
    """Resolve the number of shares to sell.

    When ``quantity`` is None the full holding is sold (unchanged behavior).
    When given, the requested partial quantity is used, clamped to the range
    [1, holding_quantity] to avoid over-selling. Used by pyramiding fractional
    sells (#288).
    """
    if quantity is None:
        return holding_quantity
    try:
        q = int(quantity)
    except (TypeError, ValueError):
        return holding_quantity
    if q <= 0:
        return holding_quantity
    return min(q, holding_quantity)


def _safe_int(value: Any, default: int = 0) -> int:
    """Coerce a KIS response field (often an empty string) to int. Never raises."""
    try:
        s = str(value).strip()
        if not s:
            return default
        return int(float(s))
    except (TypeError, ValueError):
        return default


class DomesticStockTrading:
    """Domestic stock trading class"""

    # Default buy amount per stock
    DEFAULT_BUY_AMOUNT = _cfg["default_unit_amount"]
    # Auto trading enabled flag
    AUTO_TRADING = _cfg["auto_trading"]
    # Default trading environment
    DEFAULT_MODE = _cfg["default_mode"]

    def __init__(
        self,
        mode: str = DEFAULT_MODE,
        buy_amount: int = None,
        auto_trading: bool = AUTO_TRADING,
        account_name: str = None,
        account_index: int = None,
        product_code: str = "01",
    ):
        """
        Initialize

        Args:
            mode: 'demo' (simulated investment) or 'real' (real investment)
            buy_amount: Buy amount per stock (default: refer to yaml file)
            auto_trading: Whether to execute auto trading

        Raises:
            RuntimeError: Authentication failed with detailed error message
        """
        self.mode = mode
        self.env = "vps" if mode == "demo" else "prod"
        self.auto_trading = auto_trading
        self.account_index = account_index
        self.account_config = ka.resolve_account(
            svr=self.env,
            product=str(product_code),
            account_name=account_name,
            account_index=account_index,
            market="kr",
        )
        self.account_name = self.account_config["name"]
        self.account_key = self.account_config["account_key"]
        self.account_index = account_index
        self.product_code = self.account_config["product"]
        default_buy_amount = int(self.account_config.get("buy_amount_krw") or self.DEFAULT_BUY_AMOUNT)
        self.buy_amount = buy_amount if buy_amount is not None else default_buy_amount

        # Authentication with improved error handling
        try:
            ka.auth(
                svr=self.env,
                product=self.product_code,
                account_key=self.account_key,
            )
        except CredentialMismatchError as e:
            logger.error("=" * 60)
            logger.error("❌ CREDENTIAL MISMATCH DETECTED!")
            logger.error("=" * 60)
            logger.error(f"Mode: {self.mode} (env: {self.env})")
            logger.error(f"Error: {e}")
            logger.error("")
            logger.error("📋 HOW TO FIX:")
            logger.error("   1. Open trading/config/kis_devlp.yaml")
            logger.error(f"   2. For {self.mode} mode:")
            if self.mode == "real":
                logger.error("      - 'my_app' should start with 'PS' (NOT 'PSVT')")
                logger.error("      - 'accounts'에 실전투자 계좌를 올바르게 설정하세요")
            else:
                logger.error("      - 'paper_app' should start with 'PSVT'")
                logger.error("      - 'accounts'에 모의투자 계좌를 올바르게 설정하세요")
            logger.error("=" * 60)
            raise RuntimeError(f"Credential mismatch for {self.mode} mode: {e}") from e

        except TokenRequestError as e:
            logger.error("=" * 60)
            logger.error("❌ TOKEN REQUEST FAILED!")
            logger.error("=" * 60)
            logger.error(f"Mode: {self.mode} (env: {self.env})")
            logger.error(f"Status Code: {e.status_code}")
            logger.error(f"Error: {e}")
            logger.error("")
            logger.error("📋 POSSIBLE CAUSES:")
            logger.error("   - KIS API server is temporarily unavailable (try again later)")
            logger.error("   - App key/secret are incorrect in kis_devlp.yaml")
            logger.error("   - Network connectivity issue")
            logger.error("   - Rate limit exceeded (wait a few minutes)")
            logger.error("=" * 60)
            raise RuntimeError(f"Token request failed for {self.mode} mode: {e}") from e

        except TokenFileError as e:
            logger.error("=" * 60)
            logger.error("❌ TOKEN FILE ERROR!")
            logger.error("=" * 60)
            logger.error(f"Error: {e}")
            logger.error("")
            logger.error("📋 POSSIBLE CAUSES:")
            logger.error("   - trading/config/ directory permission issue")
            logger.error("   - Disk full")
            logger.error("   - Token file locked by another process")
            logger.error("=" * 60)
            raise RuntimeError(f"Token file error for {self.mode} mode: {e}") from e

        except KISAuthError as e:
            logger.error("=" * 60)
            logger.error("❌ KIS AUTHENTICATION ERROR!")
            logger.error("=" * 60)
            logger.error(f"Mode: {self.mode}, Error: {e}")
            logger.error("📋 Please check kis_devlp.yaml settings.")
            logger.error("=" * 60)
            raise RuntimeError(f"{self.mode} mode authentication failed: {e}") from e

        # Get trading environment
        try:
            self.trenv = ka.getTREnv()
        except RuntimeError as e:
            logger.error("❌ KIS API environment not initialized!")
            logger.error(f"Mode: {self.mode}, Error: {e}")
            logger.error("📋 This usually means authentication failed silently.")
            raise RuntimeError(f"{self.mode} mode authentication failed") from e

        # Additional setup for asynchronous processing
        self._global_lock = asyncio.Lock()  # Global account access control
        self._semaphore = asyncio.Semaphore(3)  # Maximum 3 concurrent requests
        self._stock_locks = {}  # Per-stock locks

        logger.info("✅ DomesticStockTrading initialized (Async Enabled)")
        logger.info(f"   Mode: {mode}, Buy Amount: {self.buy_amount:,} KRW")
        logger.info(f"   Account: {self.account_name} ({ka.mask_account_number(self.trenv.my_acct)}-{self.trenv.my_prod})")

    def _activate_account(self):
        """Ensure the shared KIS environment matches this trader's account."""
        ka.changeTREnv(
            self.trenv.my_token,
            svr=self.env,
            product=self.trenv.my_prod,
            account_key=self.account_key,
        )

    def _request(self, api_url: str, tr_id: str, params: Dict[str, Any], **kwargs):
        with ka.get_trading_env_lock():
            self._activate_account()
            return ka._url_fetch(api_url, tr_id, "", params, **kwargs)

    def get_current_price(self, stock_code: str) -> Optional[Dict[str, Any]]:
        """
        Get current market price (also used for connectivity test)

        Args:
            stock_code: Stock code (6 digits)

        Returns:
            {
                'stock_code': 'stock code',
                'stock_name': 'stock name',
                'current_price': current price,
                'change_rate': change rate from previous day,
                'volume': trading volume
            }
        """
        api_url = "/uapi/domestic-stock/v1/quotations/inquire-price"
        tr_id = "FHKST01010100"

        params = {
            "fid_cond_mrkt_div_code": "J",
            "fid_input_iscd": stock_code
        }

        try:
            res = self._request(api_url, tr_id, params)

            if res.isOK():
                data = res.getBody().output

                result = {
                    'stock_code': stock_code,
                    'stock_name': data.get('rprs_mrkt_kor_name', ''),
                    'current_price': int(data.get('stck_prpr', 0)),  # Current price
                    'change_rate': float(data.get('prdy_ctrt', 0)),  # Change rate from previous day
                    'volume': int(data.get('acml_vol', 0)),  # Cumulative volume
                    # 종목상태/시장경고 (이벤트 강제청산 자동탐지용 — cores.corporate_status)
                    # iscd_stat_cls_code: 00/55 정상, 51 관리종목, 52 투자위험, 53 투자경고, 58 거래정지
                    'iscd_stat_cls_code': data.get('iscd_stat_cls_code', ''),
                    'mrkt_warn_cls_code': data.get('mrkt_warn_cls_code', ''),  # 00 없음/01 주의/02 경고/03 위험
                }

                logger.info(f"[{stock_code}] Current price: {result['current_price']:,} KRW ({result['change_rate']:+.2f}%)")
                return result
            else:
                logger.error(f"Failed to get current price: {res.getErrorCode()} - {res.getErrorMessage()}")
                return None

        except Exception as e:
            logger.error(f"Error getting current price: {str(e)}")
            return None

    def calculate_buy_quantity(self, stock_code: str, buy_amount: int = None) -> int:
        """
        Calculate buyable quantity

        Args:
            stock_code: Stock code
            buy_amount: Buy amount (default: amount set during initialization)

        Returns:
            Buyable quantity (0 if cannot buy)
        """
        amount = buy_amount if buy_amount else self.buy_amount

        # Get current price
        current_price_info = self.get_current_price(stock_code)
        if not current_price_info:
            return 0

        current_price = current_price_info['current_price']

        # Defensive guard: reject when the current price is missing/zero (e.g. a
        # failed quote, or a non-KR ticker mis-routed to this domestic path).
        # Without this, `amount / current_price` raises ZeroDivisionError and we
        # must NEVER attempt to buy at an unknown price.
        if not current_price or current_price <= 0:
            logger.warning(f"[{stock_code}] Invalid current price ({current_price}) - rejecting buy")
            return 0

        # Calculate buyable quantity (floor division)
        current_quantity = math.floor(amount / current_price)

        if current_quantity == 0:
            logger.warning(f"[{stock_code}] Current price {current_price:,} KRW > Buy amount {amount:,} KRW - Cannot buy")
        else:
            total_amount = current_quantity * current_price
            logger.info(f"[{stock_code}] Can buy: {current_quantity} shares x {current_price:,} KRW = {total_amount:,} KRW")

        return current_quantity

    def buy_market_price(self, stock_code: str, buy_amount: int = None) -> Dict[str, Any]:
        """
        Buy at market price

        Args:
            stock_code: Stock code
            buy_amount: Buy amount (default: amount set during initialization)

        Returns:
            {
                'success': Success status,
                'order_no': Order number,
                'stock_code': Stock code,
                'quantity': Order quantity,
                'message': Message
            }
        """

        if not self.auto_trading:
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': 0,
                'message': 'Auto trading is disabled. Cannot execute buy order. (AUTO_TRADING=False)'
            }


        # Calculate buyable quantity
        buy_quantity = self.calculate_buy_quantity(stock_code, buy_amount)

        if buy_quantity == 0:
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': 0,
                'message': 'Buyable quantity is 0 (current price is higher than buy amount)'
            }

        # Execute buy order
        api_url = "/uapi/domestic-stock/v1/trading/order-cash"

        # Set TR ID (real/demo distinction)
        if self.mode == "real":
            tr_id = "TTTC0012U"  # Real buy
        else:
            tr_id = "VTTC0012U"  # Demo buy

        params = {
            "CANO": self.trenv.my_acct,
            "ACNT_PRDT_CD": self.trenv.my_prod,
            "PDNO": stock_code,
            "ORD_DVSN": "01",  # 01: Market price
            "ORD_QTY": str(buy_quantity),
            "ORD_UNPR": "0",  # 0 for market price
            "EXCG_ID_DVSN_CD": "KRX",
            "SLL_TYPE": "",
            "CNDT_PRIC": ""
        }

        try:
            res = self._request(api_url, tr_id, params, postFlag=True)

            if res.isOK():
                output = res.getBody().output
                order_no = output.get('odno', '')

                logger.info(f"[{stock_code}] Market buy order successful: {buy_quantity} shares, order no: {order_no}")

                return {
                    'success': True,
                    'order_no': order_no,
                    'stock_code': stock_code,
                    'quantity': buy_quantity,
                    'message': f'Market buy order completed ({buy_quantity} shares)'
                }
            else:
                error_msg = f"{res.getErrorCode()} - {res.getErrorMessage()}"
                logger.error(f"Buy order failed: {error_msg}")

                return {
                    'success': False,
                    'order_no': None,
                    'stock_code': stock_code,
                    'quantity': buy_quantity,
                    'message': f'Buy order failed: {error_msg}'
                }

        except Exception as e:
            logger.error(f"Error during buy order: {str(e)}")
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': buy_quantity,
                'message': f'Error during buy order: {str(e)}'
            }

    def get_holding_quantity(self, stock_code: str) -> int:
        """
        Get holding quantity for a specific stock

        Args:
            stock_code: Stock code

        Returns:
            Holding quantity (0 if none)
        """
        current_portfolio = self.get_portfolio()

        for current_stock in current_portfolio:
            if current_stock['stock_code'] == stock_code:
                return current_stock['quantity']

        return 0

    def buy_limit_price(self, stock_code: str, limit_price: int, buy_amount: int = None) -> Dict[str, Any]:
        """
        Buy at limit price

        Args:
            stock_code: Stock code
            limit_price: Limit price
            buy_amount: Buy amount (default: amount set during initialization)

        Returns:
            {
                'success': Success status,
                'order_no': Order number,
                'stock_code': Stock code,
                'quantity': Order quantity,
                'limit_price': Limit price,
                'message': Message
            }
        """

        if not self.auto_trading:
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': 0,
                'limit_price': limit_price,
                'message': 'Auto trading is disabled. Cannot execute buy order. (AUTO_TRADING=False)'
            }

        amount = buy_amount if buy_amount else self.buy_amount

        # Defensive guard: a missing/zero limit price would raise
        # ZeroDivisionError, and we must never buy at an unknown price.
        if not limit_price or limit_price <= 0:
            logger.warning(f"[{stock_code}] Invalid limit price ({limit_price}) - rejecting buy")
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': 0,
                'limit_price': limit_price,
                'message': f'Invalid limit price ({limit_price}). Cannot execute buy order.'
            }

        # Calculate buyable quantity (based on limit price)
        buy_quantity = math.floor(amount / limit_price)

        if buy_quantity == 0:
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': 0,
                'limit_price': limit_price,
                'message': f'Buyable quantity is 0 (limit price {limit_price:,} KRW > buy amount {amount:,} KRW)'
            }

        # Execute limit price buy order
        api_url = "/uapi/domestic-stock/v1/trading/order-cash"

        if self.mode == "real":
            tr_id = "TTTC0012U"  # Real buy
        else:
            tr_id = "VTTC0012U"  # Demo buy

        params = {
            "CANO": self.trenv.my_acct,
            "ACNT_PRDT_CD": self.trenv.my_prod,
            "PDNO": stock_code,
            "ORD_DVSN": "00",  # 00: Limit price
            "ORD_QTY": str(buy_quantity),
            "ORD_UNPR": str(limit_price),  # Limit price
            "EXCG_ID_DVSN_CD": "KRX",
            "SLL_TYPE": "",
            "CNDT_PRIC": ""
        }

        try:
            res = self._request(api_url, tr_id, params, postFlag=True)

            if res.isOK():
                output = res.getBody().output
                order_no = output.get('odno', '')

                logger.info(f"[{stock_code}] Limit buy order successful: {buy_quantity} shares x {limit_price:,} KRW, order no: {order_no}")

                return {
                    'success': True,
                    'order_no': order_no,
                    'stock_code': stock_code,
                    'quantity': buy_quantity,
                    'limit_price': limit_price,
                    'message': f'Limit buy order completed ({buy_quantity} shares x {limit_price:,} KRW)'
                }
            else:
                error_msg = f"{res.getErrorCode()} - {res.getErrorMessage()}"
                logger.error(f"Limit buy order failed: {error_msg}")

                return {
                    'success': False,
                    'order_no': None,
                    'stock_code': stock_code,
                    'quantity': buy_quantity,
                    'limit_price': limit_price,
                    'message': f'Buy order failed: {error_msg}'
                }

        except Exception as e:
            logger.error(f"Error during limit buy order: {str(e)}")
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': buy_quantity,
                'limit_price': limit_price,
                'message': f'Error during buy order: {str(e)}'
            }

    def smart_buy(self, stock_code: str, buy_amount: int = None, limit_price: int = None) -> Dict[str, Any]:
        """
        Automatically buy using the optimal method based on time (excluding after-hours single price trading due to high unfilled probability)

        - 09:00~15:30: Market price buy
        - 15:40~16:00: After-hours closing price trading
        - Other times: Reserved order (next day limit price if limit_price provided)

        Args:
            stock_code: Stock code
            buy_amount: Buy amount (default: amount set during initialization)
            limit_price: Limit price for reserved order (market order if None)

        Returns:
            Buy result
        """

        if not self.auto_trading:
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': 0,
                'message': 'Auto trading is disabled. Cannot execute buy order. (AUTO_TRADING=False)'
            }

        now = _now_kst()
        order_window = _domestic_order_window(now)

        # Branch by Korean market time (KST), regardless of server/local timezone.
        if order_window == "regular":
            logger.info(f"[{stock_code}] Regular trading hours (KST) - executing market buy")
            return self.buy_market_price(stock_code, buy_amount)

        if order_window == "closing":
            logger.info(f"[{stock_code}] After-hours closing price time (KST) - executing closing price buy")
            return self.buy_closing_price(stock_code, buy_amount)

        if order_window == "reserved":
            if limit_price:
                logger.info(f"[{stock_code}] Reserved order window (KST) - executing reserved order (limit: {limit_price:,} KRW)")
            else:
                logger.info(f"[{stock_code}] Reserved order window (KST) - executing reserved order (market)")
            return self.buy_reserved_order(stock_code, buy_amount, limit_price=limit_price)

        message = "Order window unavailable in KST (reserved orders are accepted 16:00~23:40 and 00:10~07:30)"
        logger.warning(f"[{stock_code}] {message}")
        return {
            'success': False,
            'order_no': None,
            'stock_code': stock_code,
            'quantity': 0,
            'message': message
        }

    def buy_closing_price(self, stock_code: str, buy_amount: int = None) -> Dict[str, Any]:
        """
        Buy at after-hours closing price (15:40~16:00)
        Buy at closing price of the day

        Args:
            stock_code: Stock code
            buy_amount: Buy amount (default: amount set during initialization)

        Returns:
            Buy result
        """

        if not self.auto_trading:
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': 0,
                'message': 'Auto trading is disabled. Cannot execute buy order. (AUTO_TRADING=False)'
            }

        # Calculate buyable quantity
        buy_quantity = self.calculate_buy_quantity(stock_code, buy_amount)

        if buy_quantity == 0:
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': 0,
                'message': 'Buyable quantity is 0'
            }

        # After-hours closing price buy
        api_url = "/uapi/domestic-stock/v1/trading/order-cash"

        if self.mode == "real":
            tr_id = "TTTC0012U"
        else:
            tr_id = "VTTC0012U"

        params = {
            "CANO": self.trenv.my_acct,
            "ACNT_PRDT_CD": self.trenv.my_prod,
            "PDNO": stock_code,
            "ORD_DVSN": "06",  # 06: Post-market after-hours (장후 시간외, 15:40~16:00)
            "ORD_QTY": str(buy_quantity),
            "ORD_UNPR": "0",  # 0 for closing price trading
            "EXCG_ID_DVSN_CD": "KRX",
            "SLL_TYPE": "",
            "CNDT_PRIC": ""
        }

        try:
            res = self._request(api_url, tr_id, params, postFlag=True)

            if res.isOK():
                output = res.getBody().output
                order_no = output.get('odno', '')

                logger.info(f"[{stock_code}] After-hours closing price buy order successful: {buy_quantity} shares, order no: {order_no}")

                return {
                    'success': True,
                    'order_no': order_no,
                    'stock_code': stock_code,
                    'quantity': buy_quantity,
                    'message': f'After-hours closing price buy order completed ({buy_quantity} shares)'
                }
            else:
                error_msg = f"{res.getErrorCode()} - {res.getErrorMessage()}"
                logger.error(f"After-hours closing price buy failed: {error_msg}")

                return {
                    'success': False,
                    'order_no': None,
                    'stock_code': stock_code,
                    'quantity': buy_quantity,
                    'message': f'Buy order failed: {error_msg}'
                }

        except Exception as e:
            logger.error(f"Error during after-hours closing price buy: {str(e)}")
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': buy_quantity,
                'message': f'Error during buy order: {str(e)}'
            }

    def buy_reserved_order(self, stock_code: str, buy_amount: int = None, end_date: str = None, limit_price: int = None) -> Dict[str, Any]:
        """
        Buy with reserved order (auto-execute on next trading day)
        Reserved order available: 15:40~next business day 07:30 (excluding 23:40~00:10)

        Args:
            stock_code: Stock code
            buy_amount: Buy amount (default: amount set during initialization)
            end_date: Period reservation end date (YYYYMMDD format, regular reservation if None)
            limit_price: Limit price (market order if None)

        Returns:
            Buy result
        """

        if not self.auto_trading:
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': 0,
                'message': 'Auto trading is disabled. Cannot execute buy order. (AUTO_TRADING=False)'
            }

        amount = buy_amount if buy_amount else self.buy_amount

        # Set order type and unit price
        if limit_price and limit_price > 0:
            ord_dvsn_cd = "00"  # Limit price
            ord_unpr = str(int(limit_price))
            # Calculate quantity based on limit price (must be int for API)
            buy_quantity = int(amount // limit_price)
            logger.info(f"[{stock_code}] Reserved order limit price: {int(limit_price):,} KRW, quantity: {buy_quantity} shares")
        else:
            ord_dvsn_cd = "01"  # Market price
            ord_unpr = "0"
            # For market price, calculate quantity based on current price
            buy_quantity = self.calculate_buy_quantity(stock_code, amount)

        if buy_quantity == 0:
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': 0,
                'message': 'Buyable quantity is 0'
            }

        # Call reserved order API
        api_url = "/uapi/domestic-stock/v1/trading/order-resv"
        tr_id = "CTSC0008U"

        params = {
            "CANO": self.trenv.my_acct,
            "ACNT_PRDT_CD": self.trenv.my_prod,
            "PDNO": stock_code,
            "ORD_QTY": str(int(buy_quantity)),  # Must be integer string for KIS API
            "ORD_UNPR": ord_unpr,
            "SLL_BUY_DVSN_CD": "02",  # 02: Buy
            "ORD_DVSN_CD": ord_dvsn_cd,
            "ORD_OBJT_CBLC_DVSN_CD": "10",  # 10: Cash
            "LOAN_DT": "",
            "LDNG_DT": ""
        }

        # Add end date for period reservation
        if end_date:
            params["RSVN_ORD_END_DT"] = end_date
        else:
            params["RSVN_ORD_END_DT"] = ""

        try:
            res = self._request(api_url, tr_id, params, postFlag=True)

            if res.isOK():
                output = res.getBody().output
                order_no = output.get('RSVN_ORD_SEQ', '')  # Reserved order receipt number

                order_type_str = {
                    "01": "Market",
                    "00": f"Limit({ord_unpr} KRW)",
                    "05": "Pre-market after-hours"
                }.get(ord_dvsn_cd, "")

                period_str = f"Period reservation(~{end_date})" if end_date else "Regular reservation"

                logger.info(f"[{stock_code}] Reserved buy order successful: {buy_quantity} shares, {order_type_str}, {period_str}")

                return {
                    'success': True,
                    'order_no': order_no,
                    'stock_code': stock_code,
                    'quantity': buy_quantity,
                    'order_type': order_type_str,
                    'period_type': period_str,
                    'message': f'Reserved buy order completed ({buy_quantity} shares, {order_type_str}, {period_str})'
                }
            else:
                # Reserved order failed - do NOT fallback to market (doesn't work outside hours)
                # Market buy will fail with APBK0918 "장운영시간이 아닙니다" outside trading hours
                error_msg = f"{res.getErrorCode()} - {res.getErrorMessage()}"
                logger.error(f"Reserved buy order failed: {error_msg}")
                return {
                    'success': False,
                    'order_no': None,
                    'stock_code': stock_code,
                    'quantity': buy_quantity,
                    'message': f"Reserved order failed: {error_msg}"
                }

        except Exception as e:
            logger.error(f"Error during reserved buy order: {str(e)}")
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': buy_quantity,
                'message': f"Error during reserved buy order: {str(e)}"
            }

    def sell_all_market_price(self, stock_code: str, quantity: int = None) -> Dict[str, Any]:
        """
        Sell at market price.

        Args:
            stock_code: Stock code
            quantity: Partial sell quantity. When None, sells the entire holding
                (unchanged behavior). When given, sells exactly that many shares
                (clamped to the current holding).

        Returns:
            {
                'success': Success status,
                'order_no': Order number,
                'stock_code': Stock code,
                'quantity': Sell quantity,
                'message': Message
            }
        """

        if not self.auto_trading:
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': 0,
                'message': 'Auto trading is disabled. Cannot execute sell order. (AUTO_TRADING=False)'
            }

        # Check holding quantity
        holding_quantity = self.get_holding_quantity(stock_code)

        if holding_quantity == 0:
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': 0,
                'message': 'No holding quantity'
            }

        # Determine sell quantity (partial when quantity given, else full holding)
        buy_quantity = _resolve_sell_quantity(holding_quantity, quantity)

        # Execute sell order
        api_url = "/uapi/domestic-stock/v1/trading/order-cash"

        # Set TR ID (real/demo distinction)
        if self.mode == "real":
            tr_id = "TTTC0011U"  # Real sell
        else:
            tr_id = "VTTC0011U"  # Demo sell

        params = {
            "CANO": self.trenv.my_acct,
            "ACNT_PRDT_CD": self.trenv.my_prod,
            "PDNO": stock_code,
            "ORD_DVSN": "01",  # 01: Market price
            "ORD_QTY": str(buy_quantity),
            "ORD_UNPR": "0",  # 0 for market price
            "EXCG_ID_DVSN_CD": "KRX",
            "SLL_TYPE": "01",  # 01: Regular sell
            "CNDT_PRIC": ""
        }

        try:
            res = self._request(api_url, tr_id, params, postFlag=True)

            if res.isOK():
                output = res.getBody().output
                order_no = output.get('odno', '')

                logger.info(f"[{stock_code}] Market sell all order successful: {buy_quantity} shares, order no: {order_no}")

                return {
                    'success': True,
                    'order_no': order_no,
                    'stock_code': stock_code,
                    'quantity': buy_quantity,
                    'message': f'Market sell all order completed ({buy_quantity} shares)'
                }
            else:
                error_msg = f"{res.getErrorCode()} - {res.getErrorMessage()}"
                logger.error(f"Sell order failed: {error_msg}")

                return {
                    'success': False,
                    'order_no': None,
                    'stock_code': stock_code,
                    'quantity': buy_quantity,
                    'message': f'Sell order failed: {error_msg}'
                }

        except Exception as e:
            logger.error(f"Error during sell order: {str(e)}")
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': buy_quantity,
                'message': f'Error during sell order: {str(e)}'
            }

    def smart_sell_all(self, stock_code: str, limit_price: int = None, quantity: int = None) -> Dict[str, Any]:
        """
        Automatically sell using the optimal method based on time (excluding after-hours single price trading due to high unfilled probability)

        - 09:00~15:30: Market price sell
        - 15:40~16:00: After-hours closing price trading
        - Other times: Reserved order (next day limit price if limit_price provided)

        Args:
            stock_code: Stock code
            limit_price: Limit price for reserved order (market order if None)
            quantity: Partial sell quantity (None = full holding, unchanged behavior)

        Returns:
            Sell result
        """

        if not self.auto_trading:
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': 0,
                'message': 'Auto trading is disabled. Cannot execute sell order. (AUTO_TRADING=False)'
            }

        now = _now_kst()
        order_window = _domestic_order_window(now)

        # Branch by Korean market time (KST), regardless of server/local timezone.
        if order_window == "regular":
            logger.info(f"[{stock_code}] Regular trading hours (KST) - executing market sell")
            return self.sell_all_market_price(stock_code, quantity=quantity)

        if order_window == "closing":
            logger.info(f"[{stock_code}] After-hours closing price time (KST) - executing closing price sell")
            return self.sell_all_closing_price(stock_code, quantity=quantity)

        if order_window == "reserved":
            if limit_price:
                logger.info(f"[{stock_code}] Reserved order window (KST) - executing reserved order (limit: {limit_price:,} KRW)")
            else:
                logger.info(f"[{stock_code}] Reserved order window (KST) - executing reserved order (market)")
            return self.sell_all_reserved_order(stock_code, limit_price=limit_price, quantity=quantity)

        message = "Order window unavailable in KST (reserved orders are accepted 16:00~23:40 and 00:10~07:30)"
        logger.warning(f"[{stock_code}] {message}")
        return {
            'success': False,
            'order_no': None,
            'stock_code': stock_code,
            'quantity': 0,
            'message': message
        }

    def sell_all_closing_price(self, stock_code: str, quantity: int = None) -> Dict[str, Any]:
        """
        Sell at after-hours closing price (15:40~16:00)
        Sell at closing price of the day

        Args:
            stock_code: Stock code
            quantity: Partial sell quantity (None = full holding, unchanged)
        """
        if not self.auto_trading:
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': 0,
                'message': 'Auto trading is disabled. Cannot execute sell order. (AUTO_TRADING=False)'
            }

        # Check holding quantity
        holding_quantity = self.get_holding_quantity(stock_code)

        if holding_quantity == 0:
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': 0,
                'message': 'No holding quantity'
            }

        buy_quantity = _resolve_sell_quantity(holding_quantity, quantity)

        # After-hours closing price sell
        api_url = "/uapi/domestic-stock/v1/trading/order-cash"

        if self.mode == "real":
            tr_id = "TTTC0011U"
        else:
            tr_id = "VTTC0011U"

        params = {
            "CANO": self.trenv.my_acct,
            "ACNT_PRDT_CD": self.trenv.my_prod,
            "PDNO": stock_code,
            "ORD_DVSN": "06",  # 06: Post-market after-hours
            "ORD_QTY": str(buy_quantity),
            "ORD_UNPR": "0",  # 0 for closing price trading
            "EXCG_ID_DVSN_CD": "KRX",
            "SLL_TYPE": "01",
            "CNDT_PRIC": ""
        }

        try:
            res = self._request(api_url, tr_id, params, postFlag=True)

            if res.isOK():
                output = res.getBody().output
                order_no = output.get('odno', '')

                logger.info(f"[{stock_code}] After-hours closing price sell order successful: {buy_quantity} shares, order no: {order_no}")

                return {
                    'success': True,
                    'order_no': order_no,
                    'stock_code': stock_code,
                    'quantity': buy_quantity,
                    'message': f'After-hours closing price sell completed ({buy_quantity} shares)'
                }
            else:
                error_msg = f"{res.getErrorCode()} - {res.getErrorMessage()}"
                return {
                    'success': False,
                    'order_no': None,
                    'stock_code': stock_code,
                    'quantity': buy_quantity,
                    'message': f'Sell failed: {error_msg}'
                }

        except Exception as e:
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': buy_quantity,
                'message': f'Error during sell: {str(e)}'
            }

    def sell_all_reserved_order(self, stock_code: str, end_date: str = None, limit_price: int = None, quantity: int = None) -> Dict[str, Any]:
        """
        Sell with reserved order (auto-execute on next trading day)
        Reserved order available: 15:40~next business day 07:30 (excluding 23:40~00:10)

        Args:
            stock_code: Stock code
            end_date: Period reservation end date (YYYYMMDD format, regular reservation if None)
            limit_price: Limit price (market order if None)
            quantity: Partial sell quantity (None = full holding, unchanged)

        Returns:
            Sell result
        """

        if not self.auto_trading:
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': 0,
                'message': 'Auto trading is disabled. Cannot execute sell order. (AUTO_TRADING=False)'
            }

        # Check holding quantity
        holding_quantity = self.get_holding_quantity(stock_code)
        if holding_quantity == 0:
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': 0,
                'message': 'No holding quantity'
            }

        buy_quantity = _resolve_sell_quantity(holding_quantity, quantity)

        # Set order type and unit price
        if limit_price and limit_price > 0:
            ord_dvsn_cd = "00"  # Limit price
            ord_unpr = str(int(limit_price))
            logger.info(f"[{stock_code}] Reserved sell order limit price: {int(limit_price):,} KRW, quantity: {buy_quantity} shares")
        else:
            ord_dvsn_cd = "01"  # Market price
            ord_unpr = "0"

        # Call reserved order API
        api_url = "/uapi/domestic-stock/v1/trading/order-resv"
        tr_id = "CTSC0008U"

        params = {
            "CANO": self.trenv.my_acct,
            "ACNT_PRDT_CD": self.trenv.my_prod,
            "PDNO": stock_code,
            "ORD_QTY": str(int(buy_quantity)),  # Must be integer string for KIS API
            "ORD_UNPR": ord_unpr,
            "SLL_BUY_DVSN_CD": "01",  # 01: Sell
            "ORD_DVSN_CD": ord_dvsn_cd,
            "ORD_OBJT_CBLC_DVSN_CD": "10",  # 10: Cash
            "LOAN_DT": "",
            "LDNG_DT": ""
        }

        # Add end date for period reservation
        if end_date:
            params["RSVN_ORD_END_DT"] = end_date
        else:
            params["RSVN_ORD_END_DT"] = ""

        try:
            res = self._request(api_url, tr_id, params, postFlag=True)

            if res.isOK():
                output = res.getBody().output
                order_no = output.get('RSVN_ORD_SEQ', '')  # Reserved order receipt number

                order_type_str = {
                    "01": "Market",
                    "00": f"Limit({ord_unpr} KRW)",
                    "05": "Pre-market after-hours"
                }.get(ord_dvsn_cd, "")

                period_str = f"Period reservation(~{end_date})" if end_date else "Regular reservation"

                logger.info(f"[{stock_code}] Reserved sell order successful: {buy_quantity} shares, {order_type_str}, {period_str}")

                return {
                    'success': True,
                    'order_no': order_no,
                    'stock_code': stock_code,
                    'quantity': buy_quantity,
                    'order_type': order_type_str,
                    'period_type': period_str,
                    'message': f'Reserved sell order completed ({buy_quantity} shares, {order_type_str}, {period_str})'
                }
            else:
                # Reserved order failed - do NOT fallback to market (doesn't work outside hours)
                # Market sell will fail with APBK0918 "장운영시간이 아닙니다" outside trading hours
                error_msg = f"{res.getErrorCode()} - {res.getErrorMessage()}"
                logger.error(f"Reserved sell order failed: {error_msg}")
                return {
                    'success': False,
                    'order_no': None,
                    'stock_code': stock_code,
                    'quantity': buy_quantity,
                    'message': f"Reserved order failed: {error_msg}"
                }

        except Exception as e:
            logger.error(f"Error during reserved sell order: {str(e)}")
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'quantity': buy_quantity,
                'message': f"Error during reserved sell order: {str(e)}"
            }

    async def _get_stock_lock(self, stock_code: str) -> asyncio.Lock:
        """Return per-stock lock (prevent concurrent trading)"""
        if stock_code not in self._stock_locks:
            self._stock_locks[stock_code] = asyncio.Lock()
        return self._stock_locks[stock_code]

    async def async_buy_stock(self, stock_code: str, buy_amount: Optional[int] = None, timeout: float = 30.0, limit_price: Optional[int] = None) -> Dict[str, Any]:
        """
        Async buy API (with timeout)
        Get current price → Calculate buyable quantity → Market buy

        Args:
            stock_code: Stock code (6 digits)
            buy_amount: Buy amount (default: amount set during initialization)
            timeout: Timeout in seconds
            limit_price: Limit price for reserved order (market order if None)

        Returns:
            {
                'success': Success status,
                'stock_code': Stock code,
                'current_price': Current price at buy time,
                'quantity': Buy quantity,
                'total_amount': Total buy amount,
                'order_no': Order number,
                'message': Result message,
                'timestamp': Execution time
            }
        """
        try:
            return await asyncio.wait_for(
                self._execute_buy_stock(stock_code, buy_amount, limit_price),
                timeout=timeout
            )
        except asyncio.TimeoutError:
            return {
                'success': False,
                'stock_code': stock_code,
                'current_price': 0,
                'quantity': 0,
                'total_amount': 0,
                'order_no': None,
                'message': f'Buy request timeout ({timeout}s)',
                'timestamp': _now_kst().isoformat()
            }

    async def _execute_buy_stock(self, stock_code: str, buy_amount: int = None, limit_price: int = None) -> Dict[str, Any]:
        # Use class default if buy_amount is None
        amount = buy_amount if buy_amount else self.buy_amount

        result = {
            'success': False,
            'stock_code': stock_code,
            'current_price': 0,
            'quantity': 0,
            'total_amount': 0,
            'order_no': None,
            'message': '',
            'timestamp': _now_kst().isoformat()
        }

        # 3-level protection: per-stock lock + semaphore + global lock
        stock_lock = await self._get_stock_lock(stock_code)

        async with stock_lock:  # Level 1: Prevent concurrent trading per stock
            async with self._semaphore:  # Level 2: Limit total concurrent requests
                async with self._global_lock:  # Level 3: Protect account information
                    try:
                        logger.info(f"[Async Buy API] {stock_code} buy process started (amount: {amount:,} KRW)")

                        # Step 1: Get current price
                        current_price_info = await asyncio.to_thread(
                            self.get_current_price, stock_code
                        )
                        # Prevent rate limit
                        await asyncio.sleep(0.5)

                        if not current_price_info:
                            result['message'] = 'Failed to get current price'
                            logger.error(f"[Async Buy API] {stock_code} failed to get current price")
                            return result

                        result['current_price'] = current_price_info['current_price']

                        # Step 2: Calculate buyable quantity (use amount)
                        current_price = current_price_info['current_price']
                        buy_quantity = math.floor(amount / current_price)

                        if buy_quantity == 0:
                            result['message'] = f'Buyable quantity is 0 (buy amount: {amount:,} KRW)'
                            logger.warning(f"[Async Buy API] {stock_code} buyable quantity 0")
                            return result

                        result['quantity'] = buy_quantity
                        result['total_amount'] = buy_quantity * current_price_info['current_price']

                        # Step 3: Execute buy (use amount, limit price if provided)
                        # Use current_price as limit_price fallback for reserved orders (outside market hours)
                        # CRITICAL: Convert to int - KIS API requires integer strings, not float strings ("30800" not "30800.0")
                        effective_limit_price = int(limit_price) if (limit_price and limit_price > 0) else int(current_price)

                        # Prevent rate limit
                        await asyncio.sleep(0.5)
                        if limit_price:
                            logger.info(f"[Async Buy API] {stock_code} executing reserved buy order: {buy_quantity} shares x {effective_limit_price:,} KRW (limit)")
                        else:
                            logger.info(f"[Async Buy API] {stock_code} executing with effective limit price: {buy_quantity} shares x {effective_limit_price:,} KRW")
                        buy_result = await asyncio.to_thread(
                            self.smart_buy, stock_code, amount, effective_limit_price
                        )

                        if buy_result['success']:
                            result['success'] = True
                            result['order_no'] = buy_result['order_no']
                            result['message'] = f"Buy completed: {buy_quantity} shares x {current_price_info['current_price']:,} KRW = {result['total_amount']:,} KRW"
                            logger.info(f"[Async Buy API] {stock_code} buy successful")
                        else:
                            result['message'] = f"Buy failed: {buy_result['message']}"
                            logger.error(f"[Async Buy API] {stock_code} buy failed: {buy_result['message']}")

                    except Exception as e:
                        result['message'] = f'Error during async buy API execution: {str(e)}'
                        logger.error(f"[Async Buy API] {stock_code} error: {str(e)}")

                    # Delay to prevent API overload
                    await asyncio.sleep(0.1)

        return result

    async def async_sell_stock(self, stock_code: str, timeout: float = 30.0, limit_price: Optional[int] = None, quantity: Optional[int] = None) -> Dict[str, Any]:
        """
        Async sell API (with timeout)
        Sell holding quantity (full by default, partial when quantity given)

        Args:
            stock_code: Stock code (6 digits)
            timeout: Timeout in seconds
            limit_price: Limit price for reserved order (market order if None)
            quantity: Partial sell quantity (None = full holding, unchanged behavior)

        Returns:
            {
                'success': Success status,
                'stock_code': Stock code,
                'current_price': Current price at sell time,
                'quantity': Sell quantity,
                'estimated_amount': Estimated sell amount,
                'order_no': Order number,
                'message': Result message,
                'timestamp': Execution time
            }
        """
        try:
            return await asyncio.wait_for(
                self._execute_sell_stock(stock_code, limit_price, quantity=quantity),
                timeout=timeout
            )
        except asyncio.TimeoutError:
            return {
                'success': False,
                'stock_code': stock_code,
                'current_price': 0,
                'quantity': 0,
                'estimated_amount': 0,
                'order_no': None,
                'message': f'Sell request timeout ({timeout}s)',
                'timestamp': _now_kst().isoformat()
            }

    async def _execute_sell_stock(self, stock_code: str, limit_price: int = None, quantity: int = None) -> Dict[str, Any]:
        """Actual sell execution logic (includes portfolio verification defensive logic)

        quantity: partial sell quantity (None = full holding, unchanged behavior)
        """
        result = {
            'success': False,
            'stock_code': stock_code,
            'current_price': 0,
            'quantity': 0,
            'estimated_amount': 0,
            'order_no': None,
            'message': '',
            'timestamp': _now_kst().isoformat()
        }

        # 3-level protection: per-stock lock + semaphore + global lock
        stock_lock = await self._get_stock_lock(stock_code)

        async with stock_lock:  # Level 1: Prevent concurrent trading per stock
            async with self._semaphore:  # Level 2: Limit total concurrent requests
                async with self._global_lock:  # Level 3: Protect account information
                    try:
                        logger.info(f"[Async Sell API] {stock_code} sell process started")

                        # Defensive logic 1: Verify holding in portfolio
                        logger.info(f"[Async Sell API] {stock_code} checking portfolio...")
                        current_portfolio = await asyncio.to_thread(self.get_portfolio)

                        # Check if stock exists in portfolio
                        target_stock = None
                        for current_stock in current_portfolio:
                            if current_stock['stock_code'] == stock_code:
                                target_stock = current_stock
                                break

                        if not target_stock:
                            result['message'] = f'Stock {stock_code} not found in portfolio'
                            logger.warning(f"[Async Sell API] {stock_code} not in portfolio")
                            return result

                        if target_stock['quantity'] <= 0:
                            result['message'] = f'{stock_code} holding quantity is 0'
                            logger.warning(f"[Async Sell API] {stock_code} holding quantity 0")
                            return result

                        logger.info(f"[Async Sell API] {stock_code} holding confirmed: {target_stock['quantity']} shares")

                        # Get current price (for estimated sell amount calculation)
                        current_price_info = await asyncio.to_thread(
                            self.get_current_price, stock_code
                        )

                        if current_price_info:
                            result['current_price'] = current_price_info['current_price']
                            logger.info(f"[Async Sell API] {stock_code} current price: {current_price_info['current_price']:,} KRW")

                        # Defensive logic 2: Re-confirm holding quantity once more before selling.
                        # ⚠️ get_portfolio() returns [] on a transient balance-inquiry failure, so a
                        # single re-check could read a FALSE 0 and abort a legitimate sell (sim already
                        # deleted the row -> sim/real divergence). Distinguish "empty portfolio (likely
                        # API failure)" from "ticker genuinely absent (sold)": retry on empty, and only
                        # then fall back to the quantity confirmed by the FIRST portfolio read above.
                        prev_confirmed_qty = int(target_stock.get('quantity', 0) or 0)
                        holding_quantity = 0
                        for _chk_attempt in range(3):
                            portfolio_recheck = await asyncio.to_thread(self.get_portfolio)
                            if portfolio_recheck:  # non-empty -> trustworthy snapshot
                                _match = next(
                                    (s for s in portfolio_recheck if s.get('stock_code') == stock_code),
                                    None
                                )
                                holding_quantity = int(_match['quantity']) if _match else 0
                                break
                            # empty list -> almost certainly a transient API failure, NOT a real 0
                            logger.warning(
                                f"[Async Sell API] {stock_code} portfolio empty on final check "
                                f"(attempt {_chk_attempt + 1}/3) — retrying"
                            )
                            await asyncio.sleep(1.0)
                        else:
                            # all retries returned empty -> treat as API failure, not 0 holdings;
                            # fall back to the first-confirmed quantity so a valid sell is not dropped.
                            holding_quantity = prev_confirmed_qty
                            logger.warning(
                                f"[Async Sell API] {stock_code} final check kept returning empty portfolio "
                                f"— falling back to first-confirmed qty {prev_confirmed_qty}"
                            )

                        if holding_quantity <= 0:
                            result['message'] = f'{stock_code} holding quantity is 0 at final check'
                            logger.warning(f"[Async Sell API] {stock_code} holding quantity 0 at final check")
                            return result

                        # Execute sell all
                        # Use current_price as limit_price fallback for reserved orders (outside market hours)
                        # CRITICAL: Convert to int - KIS API requires integer strings, not float strings
                        effective_limit_price = int(limit_price) if (limit_price and limit_price > 0) else (int(result['current_price']) if result['current_price'] > 0 else None)

                        # Resolve partial sell quantity (None = full holding)
                        sell_quantity = _resolve_sell_quantity(holding_quantity, quantity)

                        if effective_limit_price:
                            logger.info(f"[Async Sell API] {stock_code} executing sell (qty: {sell_quantity}/{holding_quantity} shares, limit: {effective_limit_price:,} KRW)")
                        else:
                            logger.info(f"[Async Sell API] {stock_code} executing sell (qty: {sell_quantity}/{holding_quantity} shares, market)")
                        all_sell_result = await asyncio.to_thread(
                            self.smart_sell_all, stock_code, effective_limit_price, sell_quantity
                        )

                        if all_sell_result['success']:
                            result['success'] = True
                            result['quantity'] = all_sell_result['quantity']
                            result['order_no'] = all_sell_result['order_no']

                            # Calculate estimated sell amount
                            if result['current_price'] > 0:
                                result['estimated_amount'] = result['quantity'] * result['current_price']

                            # Add portfolio information
                            result['avg_price'] = target_stock['avg_price']
                            result['profit_amount'] = target_stock['profit_amount']
                            result['profit_rate'] = target_stock['profit_rate']

                            result['message'] = (f"Sell completed: {result['quantity']} shares "
                                                 f"(avg price: {result['avg_price']:,.0f} KRW, "
                                                 f"estimated amount: {result['estimated_amount']:,} KRW, "
                                                 f"return: {result['profit_rate']:+.2f}%)")

                            logger.info(f"[Async Sell API] {stock_code} sell successful")
                        else:
                            result['message'] = f"Sell failed: {all_sell_result['message']}"
                            logger.error(f"[Async Sell API] {stock_code} sell failed: {all_sell_result['message']}")

                    except Exception as e:
                        result['message'] = f'Error during async sell API execution: {str(e)}'
                        logger.error(f"[Async Sell API] {stock_code} error: {str(e)}")

                    # Delay to prevent API overload
                    await asyncio.sleep(0.1)

        return result

    def _request_with_retry(self, api_url: str, tr_id: str, params: Dict[str, Any], attempts: int = 3):
        """
        Retry wrapper for read-only inquiries.

        Transient gateway errors (e.g. EGW00215 "초당 거래건수 초과" per-second
        rate limit) must not surface as a normal failure: callers such as
        get_holding_quantity() interpret an empty portfolio as "no holdings"
        and abort sell orders. The per-second quota resets quickly, so a short
        backoff retry recovers. Returns the last response (caller checks isOK()).
        """
        res = None
        for attempt in range(attempts):
            if attempt:
                time.sleep(attempt)  # 1s, 2s — per-second quota resets each second
            try:
                res = self._request(api_url, tr_id, params)
            except Exception as e:
                if attempt == attempts - 1:
                    raise
                logger.warning(f"Inquiry error, retrying ({attempt + 1}/{attempts}): {e}")
                continue
            if res.isOK():
                return res
            logger.warning(
                f"Inquiry failed, retrying ({attempt + 1}/{attempts}): "
                f"{res.getErrorCode()} - {res.getErrorMessage()}"
            )
        return res

    def get_portfolio(self) -> List[Dict[str, Any]]:
        """
        Get current account portfolio

        Returns:
            [{
                'stock_code': 'stock code',
                'stock_name': 'stock name',
                'quantity': holding quantity,
                'avg_price': average price,
                'current_price': current price,
                'eval_amount': evaluation amount,
                'profit_amount': profit/loss amount,
                'profit_rate': return rate (%)
            }, ...]
        """
        api_url = "/uapi/domestic-stock/v1/trading/inquire-balance"

        # Set TR ID (real/demo distinction)
        if self.mode == "real":
            tr_id = "TTTC8434R"
        else:
            tr_id = "VTTC8434R"

        params = {
            "CANO": self.trenv.my_acct,
            "ACNT_PRDT_CD": self.trenv.my_prod,
            "AFHR_FLPR_YN": "N",
            "OFL_YN": "",
            "INQR_DVSN": "02",
            "UNPR_DVSN": "01",
            "FUND_STTL_ICLD_YN": "N",
            "FNCG_AMT_AUTO_RDPT_YN": "N",
            "PRCS_DVSN": "00",
            "CTX_AREA_FK100": "",
            "CTX_AREA_NK100": ""
        }

        try:
            res = self._request_with_retry(api_url, tr_id, params)

            if res.isOK():
                current_portfolio = []
                output1 = res.getBody().output1  # Holdings list
                output2 = res.getBody().output2[0]  # Account summary

                # Handle case when output1 is not a list
                if not isinstance(output1, list):
                    output1 = [output1] if output1 else []

                for item in output1:
                    # Only add stocks with quantity > 0
                    quantity = int(item.get('hldg_qty', 0))
                    if quantity > 0:
                        stock_info = {
                            'stock_code': item.get('pdno', ''),
                            'stock_name': item.get('prdt_name', ''),
                            'quantity': quantity,
                            'avg_price': float(item.get('pchs_avg_pric', 0)),
                            'current_price': float(item.get('prpr', 0)),
                            'eval_amount': float(item.get('evlu_amt', 0)),
                            'profit_amount': float(item.get('evlu_pfls_amt', 0)),
                            'profit_rate': float(item.get('evlu_pfls_rt', 0))
                        }
                        current_portfolio.append(stock_info)

                # Log account summary
                if output2:
                    total_eval = float(output2.get('tot_evlu_amt', 0))
                    total_profit = float(output2.get('evlu_pfls_smtl_amt', 0))
                    logger.info(f"Account total evaluation: {total_eval:,.0f} KRW, total profit/loss: {total_profit:+,.0f} KRW")

                logger.info(f"Portfolio: {len(current_portfolio)} holdings")
                return current_portfolio

            else:
                logger.error(f"Balance inquiry failed: {res.getErrorCode()} - {res.getErrorMessage()}")
                return []

        except Exception as e:
            logger.error(f"Error during balance inquiry: {str(e)}")
            return []

    def get_account_summary(self) -> None | dict[Any, Any] | dict[str, float]:
        """
        Get account summary information

        Returns:
            {
                'total_eval_amount': total evaluation amount,
                'total_profit_amount': total profit/loss,
                'total_profit_rate': total return rate,
                'deposit': deposit,
                'available_amount': available order amount
            }
        """
        api_url = "/uapi/domestic-stock/v1/trading/inquire-balance"

        # Set TR ID (real/demo distinction)
        if self.mode == "real":
            tr_id = "TTTC8434R"
        else:
            tr_id = "VTTC8434R"

        params = {
            "CANO": self.trenv.my_acct,
            "ACNT_PRDT_CD": self.trenv.my_prod,
            "AFHR_FLPR_YN": "N",
            "OFL_YN": "",
            "INQR_DVSN": "02",
            "UNPR_DVSN": "01",
            "FUND_STTL_ICLD_YN": "N",
            "FNCG_AMT_AUTO_RDPT_YN": "N",
            "PRCS_DVSN": "00",
            "CTX_AREA_FK100": "",
            "CTX_AREA_NK100": ""
        }

        try:
            res = self._request(api_url, tr_id, params)

            if res.isOK():
                output2 = res.getBody().output2[0]  # Account summary

                if output2:
                    pchs_amt = float(output2.get('pchs_amt_smtl_amt', 0)) or 1  # Replace 0 with 1

                    # Total evaluation amount and securities evaluation amount
                    tot_evlu_amt = float(output2.get('tot_evlu_amt', 0))
                    scts_evlu_amt = float(output2.get('scts_evlu_amt', 0))
                    dnca_tot_amt = float(output2.get('dnca_tot_amt', 0))

                    # Total cash (including D+2) = Total evaluation amount - Securities evaluation amount
                    # This includes deposit (D+0) + D+1 + D+2 receivables
                    total_cash = tot_evlu_amt - scts_evlu_amt

                    account_summary = {
                        'total_eval_amount': tot_evlu_amt,
                        'total_profit_amount': float(output2.get('evlu_pfls_smtl_amt', 0)),
                        'total_profit_rate': round(float(output2.get('evlu_pfls_smtl_amt', 0)) / pchs_amt * 100, 2),
                        'deposit': dnca_tot_amt,  # Deposit (D+0, same-day withdrawal available)
                        'total_cash': total_cash,  # Total cash (including D+2)
                        'available_amount': float(output2.get('ord_psbl_cash', 0))
                    }

                    logger.info(f"Account summary: Total eval {account_summary['total_eval_amount']:,.0f} KRW, "
                                f"profit/loss {account_summary['total_profit_amount']:+,.0f} KRW "
                                f"({account_summary['total_profit_rate']:+.2f}%), "
                                f"total cash(incl D+2) {account_summary['total_cash']:,.0f} KRW")

                    return account_summary

                return {}

        except Exception as e:
            logger.error(f"Error during account summary inquiry: {str(e)}")
            return {}

    # ──────────────────────────────────────────────────────────────────────────
    # Loop C prerequisites — order amend/cancel + unfilled inquiry TR wrappers.
    #
    # These mirror the existing order-cash wrappers above (same _request/auth/
    # tr_id real-vs-paper switching, same result-dict shape). They are NEW TRs
    # and were NOT exercised against live KIS at authoring time — see the
    # TODO(live-validate) markers and tasks/loop_c_design_notes.md.
    #
    # TR ids confirmed against koreainvestment/open-trading-api (2025-06-01):
    #   - amend/cancel : real TTTC0013U / paper VTTC0013U  (order-rvsecncl)
    #     ⚠️ NOT the legacy TTTC0803U/VTTC0803U.
    #   - revisable inquiry : real TTTC0084R (paper VTTC0084R UNVERIFIED)
    # ──────────────────────────────────────────────────────────────────────────
    def amend_cancel_order(
        self,
        stock_code: str,
        orgn_odno: str,
        rvse_cncl_dvsn_cd: str,
        krx_fwdg_ord_orgno: str = "",
        ord_dvsn: str = "00",
        quantity: int = 0,
        limit_price: int = 0,
        qty_all_ord_yn: str = "Y",
        excg_id_dvsn_cd: str = "KRX",
        dry_run: bool = False,
    ) -> Dict[str, Any]:
        """Amend (정정) or cancel (취소) an existing domestic order.

        KIS uses ONE TR for both, distinguished by ``rvse_cncl_dvsn_cd``:
          - "01" = 정정 (amend): supply the NEW ``limit_price`` (and ``quantity``
            if amending a partial qty).
          - "02" = 취소 (cancel): price is ignored by KIS; pass 0.

        Args:
            stock_code: 6-digit code (informational; KIS keys off orgn_odno).
            orgn_odno: Original order number (ORGN_ODNO) being amended/cancelled.
            rvse_cncl_dvsn_cd: "01" amend, "02" cancel.
            krx_fwdg_ord_orgno: Original KRX forwarding order org no
                (KRX_FWDG_ORD_ORGNO) returned when the original order was placed.
            ord_dvsn: Order division of the ORIGINAL order ("00" limit, etc).
            quantity: Quantity to amend/cancel. Ignored when qty_all_ord_yn="Y".
            limit_price: New limit price for an amend; 0 for cancel.
            qty_all_ord_yn: "Y" = whole remaining qty, "N" = partial (uses quantity).
            excg_id_dvsn_cd: Exchange id division ("KRX"/"NXT"/"SOR").
            dry_run: When True (Loop C SHADOW verification), build the FULL request
                exactly as it would be sent and RETURN it WITHOUT any auth/hashkey/
                HTTP — no network, no order. Default False = LIVE behaviour
                unchanged.

        Returns:
            Result dict: {success, order_no, stock_code, message}. When
            ``dry_run`` is True instead returns {dry_run, tr_id, api_url, params}.
        """
        if not self.auto_trading and not dry_run:
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'message': 'Auto trading is disabled. Cannot amend/cancel order. (AUTO_TRADING=False)'
            }

        api_url = "/uapi/domestic-stock/v1/trading/order-rvsecncl"

        # Real vs paper switching, exactly like order-cash above.
        if self.mode == "real":
            tr_id = "TTTC0013U"  # Real amend/cancel (NOT legacy TTTC0803U)
        else:
            tr_id = "VTTC0013U"  # Demo amend/cancel

        action = "Amend" if rvse_cncl_dvsn_cd == "01" else "Cancel"

        # TODO(live-validate): TR field layout (esp. EXCG_ID_DVSN_CD requirement,
        # ORD_UNPR semantics on cancel) unverified against live KIS. Confirmed
        # only against the KIS GitHub sample, not a live order.
        params = {
            "CANO": self.trenv.my_acct,
            "ACNT_PRDT_CD": self.trenv.my_prod,
            "KRX_FWDG_ORD_ORGNO": krx_fwdg_ord_orgno,
            "ORGN_ODNO": str(orgn_odno),
            "ORD_DVSN": ord_dvsn,
            "RVSE_CNCL_DVSN_CD": rvse_cncl_dvsn_cd,  # 01: amend, 02: cancel
            "ORD_QTY": str(int(quantity)),
            "ORD_UNPR": str(int(limit_price)),
            "QTY_ALL_ORD_YN": qty_all_ord_yn,
            "EXCG_ID_DVSN_CD": excg_id_dvsn_cd,
            "CNDT_PRIC": "",
        }

        if dry_run:
            # Loop C SHADOW verification: return the exact request that WOULD be
            # sent — tr_id + endpoint + full body — without auth/hashkey/HTTP.
            return {
                'dry_run': True,
                'tr_id': tr_id,
                'api_url': api_url,
                'params': dict(params),
            }

        try:
            res = self._request(api_url, tr_id, params, postFlag=True)

            if res.isOK():
                output = res.getBody().output
                order_no = output.get('odno', '')
                logger.info(
                    f"[{stock_code}] {action} order success: orgn={orgn_odno}, "
                    f"new_no={order_no}, price={limit_price}"
                )
                return {
                    'success': True,
                    'order_no': order_no,
                    'stock_code': stock_code,
                    'message': f'{action} order completed (orgn={orgn_odno})'
                }
            else:
                error_msg = f"{res.getErrorCode()} - {res.getErrorMessage()}"
                logger.error(f"[{stock_code}] {action} order failed: {error_msg}")
                return {
                    'success': False,
                    'order_no': None,
                    'stock_code': stock_code,
                    'message': f'{action} order failed: {error_msg}'
                }

        except Exception as e:
            logger.error(f"Error during {action.lower()} order: {str(e)}")
            return {
                'success': False,
                'order_no': None,
                'stock_code': stock_code,
                'message': f'Error during {action.lower()} order: {str(e)}'
            }

    def amend_order(self, stock_code: str, orgn_odno: str, limit_price: int,
                    krx_fwdg_ord_orgno: str = "", ord_dvsn: str = "00",
                    quantity: int = 0, qty_all_ord_yn: str = "Y",
                    dry_run: bool = False) -> Dict[str, Any]:
        """Convenience wrapper: amend (정정) an order's limit price to ``limit_price``."""
        return self.amend_cancel_order(
            stock_code=stock_code, orgn_odno=orgn_odno, rvse_cncl_dvsn_cd="01",
            krx_fwdg_ord_orgno=krx_fwdg_ord_orgno, ord_dvsn=ord_dvsn,
            quantity=quantity, limit_price=limit_price, qty_all_ord_yn=qty_all_ord_yn,
            dry_run=dry_run,
        )

    def cancel_order(self, stock_code: str, orgn_odno: str,
                     krx_fwdg_ord_orgno: str = "", ord_dvsn: str = "00",
                     quantity: int = 0, qty_all_ord_yn: str = "Y",
                     dry_run: bool = False) -> Dict[str, Any]:
        """Convenience wrapper: cancel (취소) an existing order."""
        return self.amend_cancel_order(
            stock_code=stock_code, orgn_odno=orgn_odno, rvse_cncl_dvsn_cd="02",
            krx_fwdg_ord_orgno=krx_fwdg_ord_orgno, ord_dvsn=ord_dvsn,
            quantity=quantity, limit_price=0, qty_all_ord_yn=qty_all_ord_yn,
            dry_run=dry_run,
        )

    def get_revisable_orders(self, stock_code: str = None) -> List[Dict[str, Any]]:
        """Inquire revisable/cancellable (= still-open / unfilled) orders.

        TR: 주식정정취소가능주문조회 — real TTTC0084R. Returns a normalised list of
        open orders, optionally filtered to ``stock_code``. Returns [] on any
        failure (degrade to no-op — Loop C must treat empty as "nothing to chase",
        never as "everything filled").

        Each dict: {order_no, orgn_odno, stock_code, ord_qty, ord_unpr,
                    tot_ccld_qty, psbl_qty, sll_buy_dvsn_cd, ord_dvsn,
                    krx_fwdg_ord_orgno}.
        """
        api_url = "/uapi/domestic-stock/v1/trading/inquire-psbl-rvsecncl"

        # TODO(live-validate): paper tr_id VTTC0084R is UNVERIFIED in the KIS
        # sample repo. Real TTTC0084R confirmed. Loop C runs SHADOW by default.
        if self.mode == "real":
            tr_id = "TTTC0084R"
        else:
            tr_id = "VTTC0084R"

        params = {
            "CANO": self.trenv.my_acct,
            "ACNT_PRDT_CD": self.trenv.my_prod,
            "INQR_DVSN_1": "1",
            "INQR_DVSN_2": "0",
            "CTX_AREA_FK100": "",
            "CTX_AREA_NK100": "",
        }

        out: List[Dict[str, Any]] = []
        try:
            res = self._request(api_url, tr_id, params)
            if not res.isOK():
                error_msg = f"{res.getErrorCode()} - {res.getErrorMessage()}"
                logger.warning(f"Revisable-order inquiry failed: {error_msg}")
                return out

            output1 = res.getBody().output
            if not isinstance(output1, list):
                output1 = [output1] if output1 else []

            for row in output1:
                code = str(row.get('pdno', '') or '').strip()
                if stock_code and code != stock_code:
                    continue
                out.append({
                    'order_no': row.get('odno', ''),
                    'orgn_odno': row.get('orgn_odno', ''),
                    'stock_code': code,
                    'ord_qty': _safe_int(row.get('ord_qty')),
                    'ord_unpr': _safe_int(row.get('ord_unpr')),
                    'tot_ccld_qty': _safe_int(row.get('tot_ccld_qty')),
                    'psbl_qty': _safe_int(row.get('psbl_qty')),  # cancellable/amendable qty
                    'sll_buy_dvsn_cd': row.get('sll_buy_dvsn_cd', ''),  # 01 sell / 02 buy
                    'ord_dvsn': row.get('ord_dvsn_cd', ''),
                    'krx_fwdg_ord_orgno': row.get('ord_gno_brno', ''),
                })
            return out

        except Exception as e:
            logger.warning(f"Error during revisable-order inquiry: {str(e)}")
            return out


class MultiAccountDomesticStockTrading:
    """Fan out trading orders to all configured domestic accounts for the current mode."""

    def __init__(self, mode: str, buy_amount: int = None, auto_trading: bool = DomesticStockTrading.AUTO_TRADING, product_code: str = "01"):
        self.mode = mode
        self.buy_amount = buy_amount
        self.auto_trading = auto_trading
        self.product_code = str(product_code)

        svr = "vps" if mode == "demo" else "prod"
        self.account_configs = ka.get_configured_accounts(svr=svr, product=self.product_code, market="kr")
        self._traders: dict[str, DomesticStockTrading] = {}
        self.primary_account = None
        try:
            self.primary_account = ka.resolve_account(svr=svr, product=self.product_code, market="kr")
        except ValueError:
            logger.warning("No domestic accounts configured for multi-account trading")

    def _get_trader(self, account: Dict[str, Any]) -> DomesticStockTrading:
        trader = self._traders.get(account["account_key"])
        if trader is None:
            trader = DomesticStockTrading(
                mode=self.mode,
                buy_amount=self.buy_amount,
                auto_trading=self.auto_trading,
                account_name=account["name"],
                product_code=account["product"],
            )
            self._traders[account["account_key"]] = trader
        return trader

    def _get_primary_trader(self) -> DomesticStockTrading:
        if not self.primary_account:
            raise RuntimeError("No primary domestic account configured")
        return self._get_trader(self.primary_account)

    async def async_buy_stock(self, stock_code: str, buy_amount: Optional[int] = None, timeout: float = 30.0, limit_price: Optional[int] = None) -> Dict[str, Any]:
        if not self.account_configs:
            return self._aggregate_results(stock_code, [], action="buy")
        results = []
        for account in self.account_configs:
            trader = self._get_trader(account)
            result = await trader.async_buy_stock(
                stock_code=stock_code,
                buy_amount=buy_amount,
                timeout=timeout,
                limit_price=limit_price,
            )
            result["account_name"] = account["name"]
            result["account_key"] = account["account_key"]
            results.append(result)

        return self._aggregate_results(stock_code, results, action="buy")

    async def async_sell_stock(self, stock_code: str, timeout: float = 30.0, limit_price: Optional[int] = None, quantity: Optional[int] = None) -> Dict[str, Any]:
        if not self.account_configs:
            return self._aggregate_results(stock_code, [], action="sell")
        results = []
        for account in self.account_configs:
            trader = self._get_trader(account)
            result = await trader.async_sell_stock(
                stock_code=stock_code,
                timeout=timeout,
                limit_price=limit_price,
                quantity=quantity,
            )
            result["account_name"] = account["name"]
            result["account_key"] = account["account_key"]
            results.append(result)

        return self._aggregate_results(stock_code, results, action="sell")

    def get_portfolio(self) -> List[Dict[str, Any]]:
        return self._get_primary_trader().get_portfolio()

    def get_account_summary(self) -> Dict[str, Any]:
        return self._get_primary_trader().get_account_summary()

    def get_current_price(self, stock_code: str) -> Optional[Dict[str, Any]]:
        return self._get_primary_trader().get_current_price(stock_code)

    def calculate_buy_quantity(self, stock_code: str, buy_amount: int = None) -> int:
        return self._get_primary_trader().calculate_buy_quantity(stock_code, buy_amount)

    def get_holding_quantity(self, stock_code: str) -> int:
        return self._get_primary_trader().get_holding_quantity(stock_code)

    def _aggregate_results(self, stock_code: str, results: List[Dict[str, Any]], action: str) -> Dict[str, Any]:
        success_count = sum(1 for result in results if result.get("success"))
        total_accounts = len(results)
        total_quantity = sum(result.get("quantity", 0) for result in results)
        total_amount = sum(result.get("total_amount", result.get("estimated_amount", 0)) for result in results)
        successful_accounts = [result.get("account_name") for result in results if result.get("success")]
        failed_accounts = [result.get("account_name") for result in results if not result.get("success")]

        messages = [
            f"{result.get('account_name')}: {result.get('message', '')}"
            for result in results
        ]

        if total_accounts == 0:
            return {
                "success": False,
                "partial_success": False,
                "stock_code": stock_code,
                "quantity": 0,
                "total_amount": 0,
                "estimated_amount": 0,
                "order_no": None,
                "message": f"No domestic accounts configured for {action}",
                "account_results": [],
                "successful_accounts": [],
                "failed_accounts": [],
            }

        return {
            "success": success_count == total_accounts and total_accounts > 0,
            "partial_success": 0 < success_count < total_accounts,
            "stock_code": stock_code,
            "quantity": total_quantity,
            "total_amount": total_amount,
            "estimated_amount": total_amount,
            "order_no": None,
            "message": f"{action} executed for {success_count}/{total_accounts} accounts | " + " ; ".join(messages),
            "account_results": results,
            "successful_accounts": successful_accounts,
            "failed_accounts": failed_accounts,
        }


class MultiAccountTradingContext:
    """Explicit multi-account domestic trading context."""

    def __init__(
        self,
        mode: str = DomesticStockTrading.DEFAULT_MODE,
        buy_amount: int = None,
        auto_trading: bool = DomesticStockTrading.AUTO_TRADING,
        product_code: str = "01",
    ):
        self.mode = mode
        self.buy_amount = buy_amount
        self.auto_trading = auto_trading
        self.product_code = product_code
        self.trader = None

    async def __aenter__(self):
        self.trader = MultiAccountDomesticStockTrading(
            mode=self.mode,
            buy_amount=self.buy_amount,
            auto_trading=self.auto_trading,
            product_code=self.product_code,
        )
        return self.trader

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if exc_type:
            logger.error(f"MultiAccountTradingContext error: {exc_type.__name__}: {exc_val}")


# Context manager
class AsyncTradingContext:
    """Async trading context manager (safe resource management)"""
    # Default buy amount unit
    DEFAULT_BUY_AMOUNT = _cfg["default_unit_amount"]
    # Auto trading operation status
    AUTO_TRADING = _cfg["auto_trading"]
    # Default trading environment
    DEFAULT_MODE = _cfg["default_mode"]

    def __init__(
        self,
        mode: str = DEFAULT_MODE,
        buy_amount: int = None,
        auto_trading: bool = AUTO_TRADING,
        account_name: str = None,
        account_index: int = None,
        product_code: str = "01",
    ):
        self.mode = mode
        self.buy_amount = buy_amount
        self.auto_trading = auto_trading
        self.account_name = account_name
        self.account_index = account_index
        self.product_code = product_code
        self.trader = None

    async def __aenter__(self):
        self.trader = DomesticStockTrading(
            mode=self.mode,
            buy_amount=self.buy_amount,
            auto_trading=self.auto_trading,
            account_name=self.account_name,
            account_index=self.account_index,
            product_code=self.product_code,
        )
        return self.trader

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if exc_type:
            logger.error(f"AsyncTradingContext error: {exc_type.__name__}: {exc_val}")

# ========== Test Code ==========
if __name__ == "__main__":
    """
    Usage examples and tests
    """

    # 1. Initialize
    trader = DomesticStockTrading()

    # 2. Connectivity test - get current price
    print("\n=== 1. Get Current Price (Connectivity Test) ===")
    price_info = trader.get_current_price("061040")  # RF Tech
    if price_info:
        print(f"Stock name: {price_info['stock_name']}")
        print(f"Current price: {price_info['current_price']:,} KRW")
        print(f"Change rate: {price_info['change_rate']:+.2f}%")

    # 3. Calculate buyable quantity
    print("\n=== 2. Calculate Buyable Quantity ===")
    quantity = trader.calculate_buy_quantity("061040")
    print(f"Buyable quantity: {quantity} shares")

    # 4. Market buy (CAUTION when uncommenting!)
    print("\n=== 3. Market Buy (Execute when uncommented) ===")
    # buy_result = trader.smart_buy(stock_code="061040", buy_amount=trader.buy_amount)
    # print(buy_result)

    # 5. Get portfolio
    print("\n=== 4. Get Portfolio ===")
    portfolio = trader.get_portfolio()
    for stock in portfolio:
        print(f"{stock['stock_name']}({stock['stock_code']}): "
              f"{stock['quantity']} shares, "
              f"avg price: {stock['avg_price']:,.0f} KRW, "
              f"current price: {stock['current_price']:,.0f} KRW, "
              f"return: {stock['profit_rate']:+.2f}%")

    # 6. Account summary
    print("\n=== 5. Account Summary ===")
    summary = trader.get_account_summary()
    if summary:
        print(f"Total evaluation: {summary['total_eval_amount']:,.0f} KRW")
        print(f"Total profit/loss: {summary['total_profit_amount']:+,.0f} KRW")
        print(f"Total return: {summary['total_profit_rate']:+.2f}%")
        print(f"Available order amount: {summary['available_amount']:,.0f} KRW")

    # 7. Sell all (CAUTION when uncommenting!)
    print("\n=== 6. Sell All (Execute when uncommented) ===")
    # sell_result = trader.smart_sell_all("061040")
    # print(sell_result)

# fixme : Delete below comments later
## Unit tests successful (market buy, after-hours sell need testing) -> integrated into trading functions (ok) -> call trading functions in tracking_agent (ok) -> send account summary to Telegram in orchestrator (need testing)
