import json
from typing import Optional, List, Union, Dict, Annotated, Literal
import ccxt.async_support as ccxtasync # Changed for asynchronous support and alias
from fastmcp import FastMCP
import asyncio
from pydantic import Field
import pandas as pd # Added for DataFrame manipulation

# --- Constants for CCXT Exception Handling ---
CCXT_GENERAL_EXCEPTIONS = (
    ccxtasync.AuthenticationError, # Covers PermissionDenied, AccountNotEnabled, AccountSuspended
    ccxtasync.ArgumentsRequired,
    ccxtasync.BadRequest,          # Covers BadSymbol
    ccxtasync.InsufficientFunds,
    ccxtasync.InvalidAddress,      # Covers AddressPending
    ccxtasync.InvalidOrder,        # Covers OrderNotFound, OrderNotCached, etc.
    # NotSupported is handled specif
    ccxtasync.NetworkError,        # Covers DDoSProtection, RateLimitExceeded, ExchangeNotAvailable, InvalidNonce, RequestTimeout, OnMaintenance, ChecksumError
    ccxtasync.BadResponse,         # Covers NullResponse
    ccxtasync.CancelPending,
    ccxtasync.ExchangeError,       # General ccxt exchange error, placed after more specific ones
    ValueError
)
TimeframeLiteral = Literal[
    '1m', '3m', '5m', '15m', '30m', 
    '1h', '2h', '4h', '6h', '8h', '12h', 
    '1d', '3d', '1w', '1M'
]

# Initialize FastMCP
mcp = FastMCP("CCXT MCP Server 🚀")

app = mcp.streamable_http_app()

import pandas as pd
from typing import Dict, Optional, Tuple
import numpy as np

def compute_rsi(df: pd.DataFrame, length: int = 14, price_source: str = 'close') -> Optional[pd.Series]:
    """Calculates Relative Strength Index (RSI) using pandas.
    Args:
        df: Pandas DataFrame with OHLCV data, indexed by timestamp.
        length: The period for RSI calculation.
        price_source: The DataFrame column to use for price (e.g., 'close', 'hlc3').
    Returns:
        Pandas Series with RSI values, or None if calculation fails.
    """
    if price_source not in df.columns:
        raise ValueError(f"Price source column '{price_source}' not found in DataFrame.")
    if df[price_source].isnull().all():
        # print(f"Warning: Price source column '{price_source}' for RSI is all NaN.")
        return None
    try:
        delta = df[price_source].diff(1)
        gain = delta.where(delta > 0, 0)
        loss = -delta.where(delta < 0, 0)

        # Calculate initial average gain and loss using SMA for the first period
        avg_gain = gain.rolling(window=length, min_periods=length).mean()
        avg_loss = loss.rolling(window=length, min_periods=length).mean()

        # For subsequent periods, use Wilder's smoothing method (equivalent to EMA with alpha = 1/length)
        # For pandas EWM, alpha = 2 / (span + 1), so span = (2 / alpha) - 1 = 2*length - 1
        # However, it's more direct to use the recursive formula after the first value.
        
        # Fill NaN for the first `length` periods because rolling mean needs `length` values
        # For the very first RSI value, avg_gain and avg_loss are simple averages.
        # Subsequent values are smoothed.

        for i in range(length, len(df)):
            avg_gain.iloc[i] = (avg_gain.iloc[i-1] * (length - 1) + gain.iloc[i]) / length
            avg_loss.iloc[i] = (avg_loss.iloc[i-1] * (length - 1) + loss.iloc[i]) / length

        rs = avg_gain / avg_loss
        rsi = 100 - (100 / (1 + rs))
        # RSI can be NaN if avg_loss is 0.
        # If avg_loss is 0 and avg_gain is also 0, rs is NaN, rsi is NaN.
        # If avg_loss is 0 and avg_gain is > 0, rs is inf, 100 / (1 + inf) is 0, so RSI is 100.
        # To maintain consistency with other indicators that have initial NaNs,
        # we will let NaNs propagate and handle them during the first_valid_index logic.
        # rsi.fillna(100, inplace=True) # Removed: Let initial NaNs remain
        # rsi[avg_loss == 0] = 100 # Removed for consistency, will be NaN if rs is NaN or inf if avg_loss is 0.
                                  # Or, if we want to be strict to definition:
        rsi.loc[avg_loss == 0] = 100.0 # Set to 100 where avg_loss is 0 and avg_gain > 0 (rs is inf)
        # If both are 0, rs is nan, rsi remains nan. This is fine.
        
        return rsi

    except Exception as e:
        print(f"Error calculating RSI: {e}")
        return None

def compute_sma(df: pd.DataFrame, length: int = 20, price_source: str = 'close') -> Optional[pd.Series]:
    """Calculates Simple Moving Average (SMA) using pandas.
    Args:
        df: Pandas DataFrame with OHLCV data, indexed by timestamp.
        length: The period for SMA calculation.
        price_source: The DataFrame column to use for price (e.g., 'close', 'hlc3').
    Returns:
        Pandas Series with SMA values, or None if calculation fails.
    """
    if price_source not in df.columns:
        raise ValueError(f"Price source column '{price_source}' not found in DataFrame.")
    if df[price_source].isnull().all():
        # print(f"Warning: Price source column '{price_source}' for SMA is all NaN.")
        return None
    try:
        sma_series = df[price_source].rolling(window=length, min_periods=length).mean()
        return sma_series
    except Exception as e:
        print(f"Error calculating SMA: {e}")
        return None

def compute_ema(df: pd.DataFrame, length: int = 20, price_source: str = 'close') -> Optional[pd.Series]:
    """Calculates Exponential Moving Average (EMA) using pandas.
    Args:
        df: Pandas DataFrame with OHLCV data, indexed by timestamp.
        length: The span for EMA calculation.
        price_source: The DataFrame column to use for price (e.g., 'close', 'hlc3').
    Returns:
        Pandas Series with EMA values, or None if calculation fails.
    """
    if price_source not in df.columns:
        raise ValueError(f"Price source column '{price_source}' not found in DataFrame.")
    if df[price_source].isnull().all():
        # print(f"Warning: Price source column '{price_source}' for EMA is all NaN.")
        return None
    try:
        ema_series = df[price_source].ewm(span=length, adjust=False, min_periods=length).mean()
        return ema_series
    except Exception as e:
        print(f"Error calculating EMA: {e}")
        return None

def compute_macd(
    df: pd.DataFrame, 
    fast_length: int = 12, 
    slow_length: int = 26, 
    signal_length: int = 9, 
    price_source: str = 'close'
) -> Optional[Tuple[pd.Series, pd.Series, pd.Series]]:
    """Calculates Moving Average Convergence Divergence (MACD) using pandas.
    Args:
        df: Pandas DataFrame with OHLCV data, indexed by timestamp.
        fast_length: The period for the fast EMA.
        slow_length: The period for the slow EMA.
        signal_length: The period for the signal line EMA.
        price_source: The DataFrame column to use for price.
    Returns:
        A tuple of (macd_line, signal_line, histogram), or None if calculation fails.
    """
    if price_source not in df.columns:
        raise ValueError(f"Price source column '{price_source}' not found in DataFrame.")

    try:
        ema_fast = df[price_source].ewm(span=fast_length, adjust=False, min_periods=fast_length).mean()
        ema_slow = df[price_source].ewm(span=slow_length, adjust=False, min_periods=slow_length).mean()
        
        macd_line = ema_fast - ema_slow
        signal_line = macd_line.ewm(span=signal_length, adjust=False, min_periods=signal_length).mean()
        histogram = macd_line - signal_line
        
        return macd_line, signal_line, histogram
    except Exception as e:
        print(f"Error calculating MACD: {e}")
        return None

def compute_bbands(
    df: pd.DataFrame, 
    length: int = 20, 
    std_dev: float = 2.0, 
    price_source: str = 'close'
) -> Optional[Tuple[pd.Series, pd.Series, pd.Series]]:
    """Calculates Bollinger Bands (BBANDS) using pandas.
    Args:
        df: Pandas DataFrame with OHLCV data, indexed by timestamp.
        length: The period for the middle band (SMA) and standard deviation.
        std_dev: The number of standard deviations for the upper and lower bands.
        price_source: The DataFrame column to use for price.
    Returns:
        A tuple of (lower_band, middle_band, upper_band), or None if calculation fails.
    """
    if price_source not in df.columns:
        raise ValueError(f"Price source column '{price_source}' not found in DataFrame.")

    try:
        middle_band = df[price_source].rolling(window=length, min_periods=length).mean()
        rolling_std = df[price_source].rolling(window=length, min_periods=length).std()
        
        upper_band = middle_band + (rolling_std * std_dev)
        lower_band = middle_band - (rolling_std * std_dev)
        
        return lower_band, middle_band, upper_band
    except Exception as e:
        print(f"Error calculating BBANDS: {e}")
        return None

def compute_stochastic_oscillator(
    df: pd.DataFrame, 
    k_period: int = 14, 
    d_period: int = 3, 
    smooth_k: int = 3, 
    price_source_high: str = 'high', 
    price_source_low: str = 'low', 
    price_source_close: str = 'close'
) -> Optional[Tuple[pd.Series, pd.Series]]:
    """
    Calculates the Stochastic Oscillator (%K and %D).

    Args:
        df: Pandas DataFrame with OHLCV data, indexed by timestamp.
        k_period: The look-back period for the K calculation.
        d_period: The period for the D line (SMA of %K).
        smooth_k: The smoothing period for %K (SMA of raw %K).
        price_source_high: DataFrame column for high prices.
        price_source_low: DataFrame column for low prices.
        price_source_close: DataFrame column for close prices.

    Returns:
        A tuple of (percent_k, percent_d) pandas Series, or None if calculation fails.
    """
    if df.empty:
        # print("Warning: DataFrame is empty for Stochastic Oscillator calculation.")
        return None

    required_cols = [price_source_high, price_source_low, price_source_close]
    for col in required_cols:
        if col not in df.columns:
            raise ValueError(f"Price source column '{col}' not found in DataFrame.")
        if df[col].isnull().all():
            # print(f"Warning: Price source column '{col}' for Stochastic Oscillator is all NaN.")
            return None
            
    try:
        lowest_low = df[price_source_low].rolling(window=k_period, min_periods=k_period).min()
        highest_high = df[price_source_high].rolling(window=k_period, min_periods=k_period).max()

        delta_high_low = highest_high - lowest_low
        
        # Calculate raw %K
        # Set to 50 if delta_high_low is 0 (flat price in k_period)
        # otherwise calculate 100 * ((close - lowest_low) / delta_high_low)
        raw_k_values = np.where(
            delta_high_low == 0, 
            50.0,  # Set to 50 if no range (highest_high == lowest_low)
            100 * ((df[price_source_close] - lowest_low) / delta_high_low)
        )
        raw_k = pd.Series(raw_k_values, index=df.index)
        
        # Handle cases where raw_k might still be NaN due to NaNs in input even if delta_high_low is not 0
        # For example, if close, lowest_low, or highest_high had NaNs not caught by min_periods.
        # Or if (close - lowest_low) is NaN / non-zero_delta is NaN.
        # A common practice is to fill these with a mid-value or propagate.
        # Given the np.where, the main source of NaNs would be if inputs to np.where are NaN.
        # Rolling functions with min_periods handle initial NaNs.
        # If raw_k has NaNs after np.where, it means some input to the calculation was NaN.
        # We can fill these with 50, or propagate. Propagating is often safer.
        # However, the problem description implies filling NaNs from 0/0 with 50.
        # The `np.where(delta_high_low == 0, 50.0, ...)` handles the 0/0 case explicitly.
        # NaNs resulting from other operations (e.g. NaN in close) should ideally propagate.

        if smooth_k > 1:
            percent_k = raw_k.rolling(window=smooth_k, min_periods=smooth_k).mean()
        else:
            percent_k = raw_k

        percent_d = percent_k.rolling(window=d_period, min_periods=d_period).mean()
        
        # Ensure no leading NaNs beyond what's necessary due to rolling windows
        # This is generally handled by the rolling(min_periods=...)
        # and how process_indicator_series (if used externally) would pick first_valid_index.

        return percent_k, percent_d

    except Exception as e:
        print(f"Error calculating Stochastic Oscillator: {e}")
        return None

def compute_atr(
    df: pd.DataFrame, 
    period: int = 14, 
    price_source_high: str = 'high', 
    price_source_low: str = 'low', 
    price_source_close: str = 'close'
) -> Optional[pd.Series]:
    """
    Calculates the Average True Range (ATR).

    Args:
        df: Pandas DataFrame with OHLCV data, indexed by timestamp.
        period: The look-back period for ATR calculation.
        price_source_high: DataFrame column for high prices.
        price_source_low: DataFrame column for low prices.
        price_source_close: DataFrame column for close prices.

    Returns:
        A pandas Series with ATR values, or None if calculation fails.
    """
    if df.empty or len(df) < 1: # Check for empty or too short DataFrame
        # print("Warning: DataFrame is empty or too short for ATR calculation.")
        return None

    required_cols = [price_source_high, price_source_low, price_source_close]
    for col in required_cols:
        if col not in df.columns:
            raise ValueError(f"Price source column '{col}' not found in DataFrame.")
        if df[col].isnull().all():
            # print(f"Warning: Price source column '{col}' for ATR is all NaN.")
            return None
            
    # Check if there's enough data for at least one ATR value after considering the period
    # While ewm with min_periods handles this by returning NaNs, 
    # an explicit check for len(df) < period could be done here if strictness is desired.
    # However, typically, if len(df) is 1, TR can be H-L, but ATR would be NaN until `period` TR values exist.
    # The current logic with min_periods=period in ewm is standard.
    
    try:
        high_col = df[price_source_high]
        low_col = df[price_source_low]
        close_col = df[price_source_close]

        high_low = high_col - low_col
        high_prev_close = (high_col - close_col.shift(1)).abs()
        low_prev_close = (low_col - close_col.shift(1)).abs()

        # Create a DataFrame for TR components
        # Ensure index alignment, especially if inputs had different NaNs initially
        tr_components = [high_low, high_prev_close, low_prev_close]
        # Filter out series that are all NaN before concat, to avoid issues if a price source was valid but led to all NaN here
        tr_components_filtered = [s for s in tr_components if not s.isnull().all()]
        
        if not tr_components_filtered: # Should not happen if input column checks passed
            return None

        tr_df = pd.concat(tr_components_filtered, axis=1)
        true_range = tr_df.max(axis=1, skipna=False) # skipna=False to ensure NaNs propagate if all components are NaN for a row

        # Handle the first TR value: TR1 = High1 - Low1
        # .iat requires integer index, ensure df is not empty (already checked)
        if len(df) > 0: # Redundant due to earlier check, but safe
             true_range.iat[0] = high_col.iat[0] - low_col.iat[0]
        
        # Calculate ATR using Wilder's Smoothing (approximated by EWM with adjust=False)
        # min_periods=period ensures that ATR is NaN until there are `period` TR values.
        # The first ATR value will be the SMA of the first `period` TR values.
        # Subsequent values use the EMA formula.
        # Pandas ewm with adjust=False and alpha = 1/N directly implements Wilder's smoothing.
        atr = true_range.ewm(alpha=1/period, adjust=False, min_periods=period).mean()
        
        return atr

    except Exception as e:
        print(f"Error calculating ATR: {e}")
        return None

# --- Helper Function to Initialize CCXT Exchange ---
async def get_exchange_instance(
    exchange_id: str, 
    api_key_info: Optional[Dict[str, str]] = None,
    exchange_config_options: Optional[Dict] = None # Added to handle options like defaultType
) -> ccxtasync.Exchange:
    """
    Asynchronously initializes and returns a CCXT exchange instance.
    This function serves as a common utility to create authenticated or unauthenticated
    exchange instances for interacting with various cryptocurrency exchanges.

    (Note: Caching of instances is not currently implemented in this helper).

    If `api_key_info` is provided, an authenticated instance is created,
    suitable for private API calls (e.g., trading, balance fetching).
    Otherwise, an unauthenticated instance is returned, suitable for public API calls
    (e.g., fetching market data, tickers).

    Args:
        exchange_id: The lowercase string ID of the exchange (e.g., 'binance', 'kucoin', 'upbit').
                     This ID is used to dynamically load the appropriate CCXT exchange class.
        api_key_info: Optional dictionary containing API credentials.
                      Expected keys: 'apiKey', 'secret'.
                      Some exchanges might also require a 'password' (for passphrase).
                      Example: `{'apiKey': 'YOUR_API_KEY', 'secret': 'YOUR_SECRET'}`
        exchange_config_options: Optional dictionary for CCXT client configurations.
                                 This is crucial for specifying market types (e.g., spot, futures, options)
                                 or other exchange-specific settings.
                                 Example: `{'defaultType': 'future'}` for futures trading,
                                          `{'options': {'adjustForTimeDifference': True}}` for time sync.

    Returns:
        An initialized asynchronous CCXT exchange instance (`ccxtasync.Exchange`).

    Raises:
        ccxtasync.ExchangeNotFound: If the `exchange_id` does not correspond to a supported
                                     exchange in the `ccxtasync` library.
    """
    exchange_id_lower = exchange_id.lower()
    try:
        exchange_class = getattr(ccxtasync, exchange_id_lower)
    except AttributeError:
        raise ccxtasync.ExchangeNotFound(f"Exchange '{exchange_id_lower}' not found in ccxtasync library.")
    
    config = {
        'enableRateLimit': True,
        # 'verbose': True, # 디버깅 시 유용
    }
    if api_key_info:
        config.update(api_key_info)
    
    if exchange_config_options: # Merge additional exchange-specific config options
        config.update(exchange_config_options)
        
    instance = exchange_class(config)
    return instance

# --- MCP Tools for CCXT Functions (Async) ---

# Note: All tools now accept optional api_key, secret_key, and passphrase.
# However, tools performing private actions (e.g., fetching balance, creating orders)
# will return an error internally if these are not provided.

@mcp.tool(
    name="fetch_account_balance",
    description="Fetches the current balance of an account from a specified cryptocurrency exchange. "
                "API authentication (api_key, secret_key) is handled externally. "
                "Use the `params` argument to specify account type (e.g., spot, margin, futures) if the exchange requires it, "
                "or to pass other exchange-specific parameters for fetching balances.",
    tags={"account", "balance", "wallet", "funds", "private", "spot", "margin", "futures", "swap", "unified"}
)
async def fetch_balance_tool(
    exchange_id: Annotated[str, Field(description="The ID of the exchange (e.g., 'binance', 'coinbasepro', 'upbit'). Case-insensitive.")],
    api_key: Annotated[Optional[str], Field(description="Optional: Your API key for the exchange. If not directly provided, the system may use pre-configured credentials. Authentication is required for this operation.")] = None,
    secret_key: Annotated[Optional[str], Field(description="Optional: Your secret key for the exchange. Used with an API key if authentication is performed (whether keys are provided directly or pre-configured).")] = None,
    passphrase: Annotated[Optional[str], Field(description="Optional: Your API passphrase, if required by the exchange (e.g., for KuCoin, OKX). Used with an API key if authentication is performed and the exchange requires it (whether keys are provided directly or pre-configured).")] = None,
    params: Annotated[Optional[Dict], Field(description="Optional: Extra parameters for the CCXT `fetchBalance` call or for CCXT client instantiation. "
                                                        "Use this to specify market types (e.g., `{'type': 'margin'}` or `{'options': {'defaultType': 'future'}}`), "
                                                        "or pass other exchange-specific arguments. "
                                                        "Example: `{'type': 'funding'}` or `{'options': {'defaultType': 'swap'}, 'symbol': 'BTC/USDT:USDT'}` for specific balance types.")] = None
) -> Dict:
    """Internal use: Fetches account balance. Primary description is in @mcp.tool decorator."""
    if not api_key or not secret_key:
        return {"error": "API key and secret key are required for fetch_account_balance."}
    
    tool_params = params.copy() if params else {}
    api_key_info_dict = {'apiKey': api_key, 'secret': secret_key}
    if passphrase:
        api_key_info_dict['password'] = passphrase
    
    client_config_options = tool_params.pop('options', None)
    exchange : ccxtasync.Exchange = None
    try:
        exchange = await get_exchange_instance(exchange_id, api_key_info=api_key_info_dict, exchange_config_options=client_config_options)
        if not exchange.has['fetchBalance']:
            return {"error": f"Exchange '{exchange_id}' does not support fetchBalance."}
        balance = await exchange.fetchBalance(params=tool_params)
        return balance
    except CCXT_GENERAL_EXCEPTIONS as e:
        return {"error": str(e)}
    except ccxtasync.NotSupported as e: # Example of specific handling if needed, though covered by general if not separately handled
        return {"error": f"Operation Not Supported: {str(e)}"} # More specific message
    except Exception as e:
        return {"error": f"An unexpected error occurred in fetch_account_balance: {str(e)}"}
    finally:
        if exchange:
            await exchange.close()

@mcp.tool(
    name="fetch_deposit_address",
    description="Fetches the deposit address for a specific cryptocurrency on a given exchange. "
                "API authentication (api_key, secret_key) is handled externally. "
                "The `params` argument can be used to specify the network or chain if the currency supports multiple (e.g., ERC20, TRC20).",
    tags={"account", "deposit", "address", "funding", "receive", "private", "crypto"}
)
async def fetch_deposit_address_tool(
    exchange_id: Annotated[str, Field(description="The ID of the exchange (e.g., 'binance', 'kraken'). Case-insensitive.")],
    code: Annotated[str, Field(description="Currency code to fetch the deposit address for (e.g., 'BTC', 'ETH', 'USDT').")],
    api_key: Annotated[Optional[str], Field(description="Optional: Your API key for the exchange. If not directly provided, the system may use pre-configured credentials. Authentication is required for this operation.")] = None,
    secret_key: Annotated[Optional[str], Field(description="Optional: Your secret key for the exchange. Used with an API key if authentication is performed (whether keys are provided directly or pre-configured).")] = None,
    passphrase: Annotated[Optional[str], Field(description="Optional: Your API passphrase, if required by the exchange. Used with an API key if authentication is performed and the exchange requires it (whether keys are provided directly or pre-configured).")] = None,
    params: Annotated[Optional[Dict], Field(description="Optional: Extra parameters for the CCXT `fetchDepositAddress` call or for client instantiation. "
                                                        "Crucially, use this to specify the network/chain if the cryptocurrency exists on multiple networks. "
                                                        "Example: `{'network': 'TRC20'}` for USDT on Tron network, or `{'chain': 'BEP20'}`. "
                                                        "Can also include `{'options': ...}` for client-specific settings if needed.")] = None
) -> Dict:
    """Internal use: Fetches deposit address. Primary description is in @mcp.tool decorator."""
    if not api_key or not secret_key:
        return {"error": "API key and secret key are required for fetch_deposit_address."}
        
    tool_params = params.copy() if params else {}
    api_key_info_dict = {'apiKey': api_key, 'secret': secret_key}
    if passphrase:
        api_key_info_dict['password'] = passphrase
        
    client_config_options = tool_params.pop('options', None)
    exchange : ccxtasync.Exchange = None
    try:
        exchange = await get_exchange_instance(exchange_id, api_key_info=api_key_info_dict, exchange_config_options=client_config_options)
        if not exchange.has['fetchDepositAddress']:
            return {"error": f"Exchange '{exchange_id}' does not support fetchDepositAddress."}
        address_info = await exchange.fetchDepositAddress(code, params=tool_params)
        return address_info
    except CCXT_GENERAL_EXCEPTIONS as e:
        return {"error": str(e)}
    except ccxtasync.NotSupported as e:
        return {"error": f"Operation Not Supported: {str(e)}"}
    except Exception as e:
        return {"error": f"An unexpected error occurred in fetch_deposit_address: {str(e)}"}
    finally:
        if exchange:
            await exchange.close()

@mcp.tool(
    name="withdraw_cryptocurrency",
    description="Initiates a cryptocurrency withdrawal to a specified address. "
                "API authentication (api_key, secret_key) and withdrawal permissions on the API key are handled externally. "
                "Use `params` to specify the network/chain if required by the exchange or currency, and for any other exchange-specific withdrawal parameters.",
    tags={"account", "withdrawal", "transaction", "send", "crypto", "private"}
)
async def withdraw_tool(
    exchange_id: Annotated[str, Field(description="The ID of the exchange (e.g., 'binance', 'ftx'). Case-insensitive.")],
    code: Annotated[str, Field(description="Currency code for the withdrawal (e.g., 'BTC', 'ETH', 'USDT').")],
    amount: Annotated[float, Field(description="The amount of currency to withdraw. Must be greater than 0.", gt=0)],
    address: Annotated[str, Field(description="The destination address for the withdrawal.")],
    api_key: Annotated[Optional[str], Field(description="Optional: Your API key with withdrawal permissions. If not directly provided, the system may use pre-configured credentials. Authentication is required for this operation.")] = None,
    secret_key: Annotated[Optional[str], Field(description="Optional: Your secret key for the API. Used with an API key if authentication is performed (whether keys are provided directly or pre-configured).")] = None,
    tag: Annotated[Optional[str], Field(description="Optional: Destination tag, memo, or payment ID for certain currencies (e.g., XRP, XLM, EOS). Check exchange/currency requirements.")] = None,
    passphrase: Annotated[Optional[str], Field(description="Optional: API passphrase if required by the exchange for withdrawals. Used with an API key if authentication is performed and the exchange requires it (whether keys are provided directly or pre-configured).")] = None,
    params: Annotated[Optional[Dict], Field(description="Optional: Extra parameters for the CCXT `withdraw` call or for client instantiation. "
                                                        "Use this to specify the network/chain (e.g., `{'network': 'BEP20'}`), especially if the currency supports multiple. "
                                                        "May also be used for two-factor authentication codes if supported/required by the exchange via CCXT, or other specific withdrawal options. "
                                                        "Example: `{'network': 'TRC20', 'feeToUser': False}`")] = None
) -> Dict:
    """Internal use: Withdraws cryptocurrency. Primary description is in @mcp.tool decorator."""
    if not api_key or not secret_key:
        return {"error": "API key and secret key are required for withdraw_cryptocurrency."}
        
    tool_params = params.copy() if params else {}
    api_key_info_dict = {'apiKey': api_key, 'secret': secret_key}
    if passphrase:
        api_key_info_dict['password'] = passphrase

    client_config_options = tool_params.pop('options', None)
    exchange : ccxtasync.Exchange = None
    try:
        exchange = await get_exchange_instance(exchange_id, api_key_info=api_key_info_dict, exchange_config_options=client_config_options)
        if not exchange.has['withdraw']:
            return {"error": f"Exchange '{exchange_id}' does not support withdraw."}
        
        withdrawal_info = await exchange.withdraw(code, amount, address, tag, params=tool_params)
        return withdrawal_info
    except CCXT_GENERAL_EXCEPTIONS as e:
        return {"error": str(e)}
    except ccxtasync.NotSupported as e:
        return {"error": f"Operation Not Supported: {str(e)}"}
    except Exception as e:
        return {"error": f"An unexpected error occurred in withdraw_cryptocurrency: {str(e)}"}
    finally:
        if exchange:
            await exchange.close()

@mcp.tool(
    name="fetch_open_positions",
    description="Fetches currently open positions for futures, swaps, or other derivatives from an exchange. "
                "API authentication (api_key, secret_key) is handled externally. "
                "CRITICAL: The CCXT client MUST be initialized for the correct market type (e.g., futures, swap) using `params`. "
                "For example, pass `{'options': {'defaultType': 'future'}}` or `{'options': {'defaultType': 'swap'}}` in `params` if not default for the exchange.",
    tags={"trading", "positions", "futures", "derivatives", "private"}
)
async def fetch_open_positions_tool(
    exchange_id: Annotated[str, Field(description="The ID of the exchange (e.g., 'binance', 'bybit'). Case-insensitive.")],
    api_key: Annotated[Optional[str], Field(description="Optional: Your API key for the exchange. If not directly provided, the system may use pre-configured credentials. Authentication is required for this operation.")] = None,
    secret_key: Annotated[Optional[str], Field(description="Optional: Your secret key for the exchange. Used with an API key if authentication is performed (whether keys are provided directly or pre-configured).")] = None,
    passphrase: Annotated[Optional[str], Field(description="Optional: Your API passphrase, if required by the exchange (e.g., for KuCoin, OKX). Used with an API key if authentication is performed and the exchange requires it (whether keys are provided directly or pre-configured).")] = None,
    params: Annotated[Optional[Dict], Field(description="Optional: Extra parameters for the CCXT `fetchPositions` call or for client instantiation. "
                                                        "Use this to specify market types (e.g., `{'options': {'defaultType': 'future'}}` or `{'options': {'defaultType': 'swap'}}`), "
                                                        "or pass other exchange-specific arguments. "
                                                        "Example: `{'options': {'defaultType': 'future'}, 'symbols': ['BTC/USDT:USDT']}` for specific symbols.")] = None
) -> Dict:
    """Internal use: Fetches open positions. Primary description is in @mcp.tool decorator."""
    if not api_key or not secret_key:
        return {"error": "API key and secret key are required for fetch_open_positions."}
        
    tool_params = params.copy() if params else {}
    api_key_info_dict = {'apiKey': api_key, 'secret': secret_key}
    if passphrase:
        api_key_info_dict['password'] = passphrase
        
    client_config_options = tool_params.pop('options', None)
    exchange : ccxtasync.Exchange = None
    try:
        exchange = await get_exchange_instance(exchange_id, api_key_info=api_key_info_dict, exchange_config_options=client_config_options)
        if not exchange.has['fetchPositions']:
            return {"error": f"Exchange '{exchange_id}' does not support fetchPositions."}
        positions = await exchange.fetchPositions(params=tool_params)
        return positions
    except CCXT_GENERAL_EXCEPTIONS as e:
        return {"error": str(e)}
    except ccxtasync.NotSupported as e:
        return {"error": f"Operation Not Supported: {str(e)}"}
    except Exception as e:
        return {"error": f"An unexpected error occurred in fetch_open_positions: {str(e)}"}
    finally:
        if exchange:
            await exchange.close()

@mcp.tool(
    name="set_trading_leverage",
    description="Sets the leverage for a specific trading symbol, typically in futures or margin markets. "
                "API authentication (api_key, secret_key) is handled externally. "
                "CRITICAL: Ensure the CCXT client is initialized for the correct market type (e.g., futures, margin) using `params` "
                "(e.g., `{'options': {'defaultType': 'future'}}`). "
                "The `symbol` parameter may or may not be required depending on the exchange and whether setting leverage for all symbols or a specific one.",
    tags={"trading", "leverage", "futures", "margin", "private"}
)
async def set_leverage_tool(
    exchange_id: Annotated[str, Field(description="The ID of the exchange (e.g., 'binance', 'bybit'). Case-insensitive.")],
    leverage: Annotated[int, Field(description="The leverage value to set (e.g., 1, 2, 5, 10). Must be supported by the exchange.", ge=1)],
    api_key: Annotated[Optional[str], Field(description="Optional: Your API key for the exchange. If not directly provided, the system may use pre-configured credentials. Authentication is required for this operation.")] = None,
    secret_key: Annotated[Optional[str], Field(description="Optional: Your secret key for the exchange. Used with an API key if authentication is performed (whether keys are provided directly or pre-configured).")] = None,
    symbol: Annotated[Optional[str], Field(description="Optional: The trading symbol (e.g., 'BTC/USDT:USDT'). Some exchanges require this to set leverage for a specific symbol; others allow setting it globally.")] = None,
    passphrase: Annotated[Optional[str], Field(description="Optional: Your API passphrase, if required by the exchange (e.g., for KuCoin, OKX). Used with an API key if authentication is performed and the exchange requires it (whether keys are provided directly or pre-configured).")] = None,
    params: Annotated[Optional[Dict], Field(description="Optional: Extra parameters for the CCXT `setLeverage` call or for client instantiation. "
                                                        "Use this to specify market types (e.g., `{'options': {'defaultType': 'future'}}`), "
                                                        "or pass other exchange-specific arguments. "
                                                        "Example: `{'options': {'defaultType': 'future'}, 'symbol': 'BTC/USDT:USDT'}` for specific symbols.")] = None
) -> Dict:
    """Internal use: Sets trading leverage. Primary description is in @mcp.tool decorator."""
    if not api_key or not secret_key:
        return {"error": "API key and secret key are required for set_trading_leverage."}
        
    tool_params = params.copy() if params else {}
    api_key_info_dict = {'apiKey': api_key, 'secret': secret_key}
    if passphrase:
        api_key_info_dict['password'] = passphrase
        
    client_config_options = tool_params.pop('options', None)
    exchange : ccxtasync.Exchange = None
    try:
        exchange = await get_exchange_instance(exchange_id, api_key_info=api_key_info_dict, exchange_config_options=client_config_options)
        if not exchange.has['setLeverage']:
            return {"error": f"Exchange '{exchange_id}' does not support setLeverage."}
        
        leverage_result = await exchange.setLeverage(leverage, symbol, params=tool_params)
        return leverage_result
    except CCXT_GENERAL_EXCEPTIONS as e:
        return {"error": str(e)}
    except ccxtasync.NotSupported as e:
        return {"error": f"Operation Not Supported: {str(e)}"}
    except Exception as e:
        return {"error": f"An unexpected error occurred in set_trading_leverage: {str(e)}"}
    finally:
        if exchange:
            await exchange.close()

@mcp.tool(
    name="fetch_ohlcv",
    description="Fetches historical Open-High-Low-Close-Volume (OHLCV) candlestick data for a specific trading symbol and timeframe. "
                "Authentication (api_key, secret_key) is optional; some exchanges might provide more data or higher rate limits with authentication. "
                "Use `params` for exchange-specific options, like requesting 'mark' or 'index' price OHLCV for derivatives, or to set `defaultType` for client instantiation if fetching for non-spot markets.",
    tags={"market", "candles", "ohlcv", "public"}
)
async def fetch_ohlcv_tool(
    exchange_id: Annotated[str, Field(description="The ID of the exchange (e.g., 'binance', 'kucoin'). Case-insensitive.")],
    symbol: Annotated[str, Field(description="The trading symbol to fetch OHLCV data for (e.g., 'BTC/USDT', 'ETH/USDT').")],
    timeframe: Annotated[TimeframeLiteral, Field(description="The timeframe for the candlesticks (e.g., '1m', '5m', '1h', '1d').")] = '1d',
    since: Annotated[Optional[int], Field(description="Optional: The timestamp in milliseconds for the start of the range to fetch from.")] = None,
    limit: Annotated[Optional[int], Field(description="Optional: The number of candlesticks to fetch. Default is 100; max depends on exchange.")] = 100,
    api_key: Annotated[Optional[str], Field(description="Optional: Your API key for the exchange. If not directly provided, the system may use pre-configured credentials. May provide more data or higher rate limits.")] = None,
    secret_key: Annotated[Optional[str], Field(description="Optional: Your secret key for the exchange. Used with an API key if authentication is performed (whether keys are provided directly or pre-configured).")] = None,
    passphrase: Annotated[Optional[str], Field(description="Optional: Your API passphrase, if required by the exchange (e.g., for KuCoin, OKX). Used with an API key if authentication is performed and the exchange requires it (whether keys are provided directly or pre-configured).")] = None,
    params: Annotated[Optional[Dict], Field(description="Optional: Extra parameters for the CCXT `fetchOHLCV` call or for client instantiation. "
                                                        "Use this to request specific OHLCV types (e.g., `{'price': 'mark'}` for mark price) "
                                                        "or specify market types (e.g., `{'options': {'defaultType': 'future'}}`).")] = None
) -> Dict:
    """Internal use: Fetches OHLCV data. Primary description is in @mcp.tool decorator."""
    tool_params = params.copy() if params else {}
    
    # Set up API credentials if provided
    api_key_info_dict = {}
    if api_key:
        api_key_info_dict['apiKey'] = api_key
    if secret_key:
        api_key_info_dict['secret'] = secret_key
    if passphrase:
        api_key_info_dict['password'] = passphrase
    
    client_config_options = tool_params.pop('options', None)
    exchange : ccxtasync.Exchange = None
    try:
        exchange = await get_exchange_instance(exchange_id, api_key_info=api_key_info_dict or None, exchange_config_options=client_config_options)
        if not exchange.has['fetchOHLCV']:
            return {"error": f"Exchange '{exchange_id}' does not support fetchOHLCV."}
        
        ohlcv = await exchange.fetchOHLCV(symbol, timeframe, since, limit, params=tool_params)
        return ohlcv
    except CCXT_GENERAL_EXCEPTIONS as e:
        return {"error": str(e)}
    except ccxtasync.NotSupported as e:
        return {"error": f"Operation Not Supported: {str(e)}"}
    except Exception as e:
        return {"error": f"An unexpected error occurred in fetch_ohlcv: {str(e)}"}
    finally:
        if exchange:
            await exchange.close()

@mcp.tool(
    name="fetch_funding_rate",
    description="Fetches the current or historical funding rate for a perpetual futures contract symbol. "
                "Authentication is optional. "
                "CRITICAL: For many exchanges, the CCXT client must be initialized for futures/swap markets using `params` "
                "(e.g., `{'options': {'defaultType': 'future'}}`). "
                "If `fetchFundingRate` is not supported, the exchange might support `fetchFundingRates` (plural) for multiple symbols or historical rates; check error messages or use a more specific tool if available.",
    tags={"market", "funding", "futures", "public"}
)
async def fetch_funding_rate_tool(
    exchange_id: Annotated[str, Field(description="The ID of the exchange (e.g., 'binance', 'bybit'). Case-insensitive.")],
    symbol: Annotated[str, Field(description="The trading symbol for the perpetual contract (e.g., 'BTC/USDT:USDT', 'ETH/USDT:USDT').")],
    api_key: Annotated[Optional[str], Field(description="Optional: Your API key for the exchange. If not directly provided, the system may use pre-configured credentials. May provide more data or higher rate limits.")] = None,
    secret_key: Annotated[Optional[str], Field(description="Optional: Your secret key for the exchange. Used with an API key if authentication is performed (whether keys are provided directly or pre-configured).")] = None,
    passphrase: Annotated[Optional[str], Field(description="Optional: Your API passphrase, if required by the exchange (e.g., for KuCoin, OKX). Used with an API key if authentication is performed and the exchange requires it (whether keys are provided directly or pre-configured).")] = None,
    params: Annotated[Optional[Dict], Field(description="Optional: Extra parameters for the CCXT `fetchFundingRate` call or for client instantiation. "
                                                        "Use this to specify market types (e.g., `{'options': {'defaultType': 'future'}}` or `{'options': {'defaultType': 'swap'}}`), "
                                                        "or pass other exchange-specific arguments.")] = None
) -> Dict:
    """Internal use: Fetches funding rate. Primary description is in @mcp.tool decorator."""
    tool_params = params.copy() if params else {}
    
    # Set up API credentials if provided
    api_key_info_dict = {}
    if api_key:
        api_key_info_dict['apiKey'] = api_key
    if secret_key:
        api_key_info_dict['secret'] = secret_key
    if passphrase:
        api_key_info_dict['password'] = passphrase
        
    client_config_options = tool_params.pop('options', None)
    exchange : ccxtasync.Exchange = None
    try:
        exchange = await get_exchange_instance(exchange_id, api_key_info=api_key_info_dict or None, exchange_config_options=client_config_options)
        if not exchange.has['fetchFundingRate']:
            return {"error": f"Exchange '{exchange_id}' does not support fetchFundingRate."}
        
        funding_rate = await exchange.fetchFundingRate(symbol, params=tool_params)
        return funding_rate
    except CCXT_GENERAL_EXCEPTIONS as e:
        return {"error": str(e)}
    except ccxtasync.NotSupported as e:
        return {"error": f"Operation Not Supported: {str(e)}"}
    except Exception as e:
        return {"error": f"An unexpected error occurred in fetch_funding_rate: {str(e)}"}
    finally:
        if exchange:
            await exchange.close()

@mcp.tool(
    name="fetch_long_short_ratio",
    description="Fetches the long/short ratio for a symbol, typically for futures markets, by calling exchange-specific (implicit) CCXT methods. "
                "Authentication is optional. "
                "Requires specifying the `method_name` and `method_params` within the `params` argument. "
                "Client may need to be initialized for futures/swap markets via `params` (e.g., `{'options': {'defaultType': 'future'}}`).",
    tags={"market", "longshort", "ratio", "futures", "public"}
)
async def fetch_long_short_ratio_tool(
    exchange_id: Annotated[str, Field(description="The ID of the exchange (e.g., 'binance'). Case-insensitive.")],
    symbol: Annotated[str, Field(description="The trading symbol to fetch the long/short ratio for (e.g., 'BTCUSDT', 'ETHUSDT').")] = 'BTCUSDT',
    timeframe: Annotated[Optional[str], Field(description="Optional: The timeframe for the ratio (e.g., '5m', '1h', '1d').")] = '5m',
    limit: Annotated[Optional[int], Field(description="Optional: The number of data points to fetch.")] = 100,
    api_key: Annotated[Optional[str], Field(description="Optional: Your API key for the exchange. If not directly provided, the system may use pre-configured credentials. May provide more data or higher rate limits.")] = None,
    secret_key: Annotated[Optional[str], Field(description="Optional: Your secret key for the exchange. Used with an API key if authentication is performed (whether keys are provided directly or pre-configured).")] = None,
    passphrase: Annotated[Optional[str], Field(description="Optional: Your API passphrase, if required by the exchange (e.g., for KuCoin, OKX). Used with an API key if authentication is performed and the exchange requires it (whether keys are provided directly or pre-configured).")] = None,
    params: Annotated[Optional[Dict], Field(description="Required: Must contain `method_name` (e.g., 'fapiPublicGetGlobalLongShortAccountRatio') and `method_params` (e.g., {'symbol': 'BTCUSDT', 'period': '5m'}). "
                                                        "Optional: Can also include `{'options': {'defaultType': 'future'}}` for client initialization if needed.")] = None
) -> Dict:
    """Internal use: Fetches long/short ratio. Primary description is in @mcp.tool decorator."""
    if not params or 'method_name' not in params or 'method_params' not in params:
        return {"error": "The `params` argument must contain `method_name` and `method_params` for fetch_long_short_ratio."}
    
    tool_params = params.copy()
    method_name = tool_params.pop('method_name')
    method_params = tool_params.pop('method_params')
    
    # Set up API credentials if provided
    api_key_info_dict = {}
    if api_key:
        api_key_info_dict['apiKey'] = api_key
    if secret_key:
        api_key_info_dict['secret'] = secret_key
    if passphrase:
        api_key_info_dict['password'] = passphrase
        
    client_config_options = tool_params.pop('options', None)
    exchange : ccxtasync.Exchange = None
    try:
        exchange = await get_exchange_instance(exchange_id, api_key_info=api_key_info_dict or None, exchange_config_options=client_config_options)
        
        # Dynamically call the exchange-specific method
        if not hasattr(exchange, method_name):
            return {"error": f"Exchange '{exchange_id}' does not have method '{method_name}'."}
        
        method = getattr(exchange, method_name)
        result = await method(method_params)
        return result
    except CCXT_GENERAL_EXCEPTIONS as e:
        return {"error": str(e)}
    except ccxtasync.NotSupported as e:
        return {"error": f"Operation Not Supported: {str(e)}"}
    except Exception as e:
        return {"error": f"An unexpected error occurred in fetch_long_short_ratio: {str(e)}"}
    finally:
        if exchange:
            await exchange.close()

@mcp.tool(
    name="fetch_option_contract_data",
    description="Fetches market data (typically ticker data) for a specific options contract. "
                "Authentication is optional. "
                "For many exchanges, the CCXT client may need to be initialized for options markets using `params` "
                "(e.g., `{'options': {'defaultType': 'option'}}`).",
    tags={"market", "options", "public"}
)
async def fetch_option_contract_data_tool(
    exchange_id: Annotated[str, Field(description="The ID of the exchange (e.g., 'deribit'). Case-insensitive.")],
    symbol: Annotated[str, Field(description="The options contract symbol (e.g., 'BTC-28DEC23-42000-C', 'ETH-28DEC23-2400-C').")] = 'BTC-28DEC23-42000-C',
    api_key: Annotated[Optional[str], Field(description="Optional: Your API key for the exchange. If not directly provided, the system may use pre-configured credentials. May provide more data or higher rate limits.")] = None,
    secret_key: Annotated[Optional[str], Field(description="Optional: Your secret key for the exchange. Used with an API key if authentication is performed (whether keys are provided directly or pre-configured).")] = None,
    passphrase: Annotated[Optional[str], Field(description="Optional: Your API passphrase, if required by the exchange (e.g., for KuCoin, OKX). Used with an API key if authentication is performed and the exchange requires it (whether keys are provided directly or pre-configured).")] = None,
    params: Annotated[Optional[Dict], Field(description="Optional: Extra parameters for the CCXT `fetchTicker` call or for client instantiation. "
                                                        "Use this to specify market types (e.g., `{'options': {'defaultType': 'option'}}`), "
                                                        "or pass other exchange-specific arguments.")] = None
) -> Dict:
    """Internal use: Fetches option contract data. Primary description is in @mcp.tool decorator."""
    tool_params = params.copy() if params else {}
    
    # Set up API credentials if provided
    api_key_info_dict = {}
    if api_key:
        api_key_info_dict['apiKey'] = api_key
    if secret_key:
        api_key_info_dict['secret'] = secret_key
    if passphrase:
        api_key_info_dict['password'] = passphrase
        
    client_config_options = tool_params.pop('options', None)
    exchange : ccxtasync.Exchange = None
    try:
        exchange = await get_exchange_instance(exchange_id, api_key_info=api_key_info_dict or None, exchange_config_options=client_config_options)
        if not exchange.has['fetchTicker']:
            return {"error": f"Exchange '{exchange_id}' does not support fetchTicker."}
        
        ticker = await exchange.fetchTicker(symbol, params=tool_params)
        return ticker
    except CCXT_GENERAL_EXCEPTIONS as e:
        return {"error": str(e)}
    except ccxtasync.NotSupported as e:
        return {"error": f"Operation Not Supported: {str(e)}"}
    except Exception as e:
        return {"error": f"An unexpected error occurred in fetch_option_contract_data: {str(e)}"}
    finally:
        if exchange:
            await exchange.close()

@mcp.tool(
    name="fetch_market_ticker",
    description="Fetches the latest ticker data for a specific trading symbol (e.g., price, volume, spread). "
                "Authentication is optional; some exchanges might provide more data or higher rate limits with authentication. "
                "If fetching for non-spot markets (futures, options, swaps), ensure the CCXT client is initialized correctly using `params` "
                "(e.g., `{'options': {'defaultType': 'future'}}`).",
    tags={"market", "ticker", "price", "public"}
)
async def fetch_ticker_tool(
    exchange_id: Annotated[str, Field(description="The ID of the exchange (e.g., 'binance', 'coinbasepro'). Case-insensitive.")],
    symbol: Annotated[str, Field(description="The trading symbol to fetch ticker data for (e.g., 'BTC/USDT', 'ETH/USDT').")] = 'BTC/USDT',
    api_key: Annotated[Optional[str], Field(description="Optional: Your API key for the exchange. If not directly provided, the system may use pre-configured credentials. May provide more data or higher rate limits.")] = None,
    secret_key: Annotated[Optional[str], Field(description="Optional: Your secret key for the exchange. Used with an API key if authentication is performed (whether keys are provided directly or pre-configured).")] = None,
    passphrase: Annotated[Optional[str], Field(description="Optional: Your API passphrase, if required by the exchange (e.g., for KuCoin, OKX). Used with an API key if authentication is performed and the exchange requires it (whether keys are provided directly or pre-configured).")] = None,
    params: Annotated[Optional[Dict], Field(description="Optional: Extra parameters for the CCXT `fetchTicker` call or for client instantiation. "
                                                        "Use this to specify market types (e.g., `{'options': {'defaultType': 'future'}}`), "
                                                        "or pass other exchange-specific arguments.")] = None
) -> Dict:
    """Internal use: Fetches ticker data. Primary description is in @mcp.tool decorator."""
    tool_params = params.copy() if params else {}
    
    # Set up API credentials if provided
    api_key_info_dict = {}
    if api_key:
        api_key_info_dict['apiKey'] = api_key
    if secret_key:
        api_key_info_dict['secret'] = secret_key
    if passphrase:
        api_key_info_dict['password'] = passphrase
        
    client_config_options = tool_params.pop('options', None)
    exchange : ccxtasync.Exchange = None
    try:
        exchange = await get_exchange_instance(exchange_id, api_key_info=api_key_info_dict or None, exchange_config_options=client_config_options)
        if not exchange.has['fetchTicker']:
            return {"error": f"Exchange '{exchange_id}' does not support fetchTicker."}
        
        ticker = await exchange.fetchTicker(symbol, params=tool_params)
        return ticker
    except CCXT_GENERAL_EXCEPTIONS as e:
        return {"error": str(e)}
    except ccxtasync.NotSupported as e:
        return {"error": f"Operation Not Supported: {str(e)}"}
    except Exception as e:
        return {"error": f"An unexpected error occurred in fetch_market_ticker: {str(e)}"}
    finally:
        if exchange:
            await exchange.close()

@mcp.tool(
    name="fetch_public_market_trades",
    description="Fetches recent public trades for a specific trading symbol. "
                "Does not require authentication, but providing API keys might increase rate limits or access. "
                "If fetching for non-spot markets (futures, options, swaps), ensure the CCXT client is initialized correctly using `params` "
                "(e.g., `{'options': {'defaultType': 'future'}}`).",
    tags={"market", "trades", "public"}
)
async def fetch_trades_tool(
    exchange_id: Annotated[str, Field(description="The ID of the exchange (e.g., 'binance', 'kraken'). Case-insensitive.")],
    symbol: Annotated[str, Field(description="The trading symbol to fetch trades for (e.g., 'BTC/USDT', 'ETH/USDT').")] = 'BTC/USDT',
    since: Annotated[Optional[int], Field(description="Optional: The timestamp in milliseconds for the start of the range to fetch from.")] = None,
    limit: Annotated[Optional[int], Field(description="Optional: The number of trades to fetch. Default is 100; max depends on exchange.")] = 100,
    api_key: Annotated[Optional[str], Field(description="Optional: Your API key for the exchange. If not directly provided, the system may use pre-configured credentials. May provide more data or higher rate limits.")] = None,
    secret_key: Annotated[Optional[str], Field(description="Optional: Your secret key for the exchange. Used with an API key if authentication is performed (whether keys are provided directly or pre-configured).")] = None,
    passphrase: Annotated[Optional[str], Field(description="Optional: Your API passphrase, if required by the exchange (e.g., for KuCoin, OKX). Used with an API key if authentication is performed and the exchange requires it (whether keys are provided directly or pre-configured).")] = None,
    params: Annotated[Optional[Dict], Field(description="Optional: Extra parameters for the CCXT `fetchTrades` call or for client instantiation. "
                                                        "Use this to specify market types (e.g., `{'options': {'defaultType': 'future'}}`), "
                                                        "or pass other exchange-specific arguments.")] = None
) -> Dict:
    """Internal use: Fetches trades. Primary description is in @mcp.tool decorator."""
    tool_params = params.copy() if params else {}
    
    # Set up API credentials if provided
    api_key_info_dict = {}
    if api_key:
        api_key_info_dict['apiKey'] = api_key
    if secret_key:
        api_key_info_dict['secret'] = secret_key
    if passphrase:
        api_key_info_dict['password'] = passphrase
        
    client_config_options = tool_params.pop('options', None)
    exchange : ccxtasync.Exchange = None
    try:
        exchange = await get_exchange_instance(exchange_id, api_key_info=api_key_info_dict or None, exchange_config_options=client_config_options)
        if not exchange.has['fetchTrades']:
            return {"error": f"Exchange '{exchange_id}' does not support fetchTrades."}
        
        trades = await exchange.fetchTrades(symbol, since, limit, params=tool_params)
        return trades
    except CCXT_GENERAL_EXCEPTIONS as e:
        return {"error": str(e)}
    except ccxtasync.NotSupported as e:
        return {"error": f"Operation Not Supported: {str(e)}"}
    except Exception as e:
        return {"error": f"An unexpected error occurred in fetch_public_market_trades: {str(e)}"}
    finally:
        if exchange:
            await exchange.close()

@mcp.tool(
    name="create_spot_limit_order",
    description="Places a new limit order in the spot market. "
                "API authentication (api_key, secret_key) and trading permissions on the API key are handled externally. "
                "Use `params` for exchange-specific order parameters like `clientOrderId`, `postOnly`, or time-in-force policies (e.g., `{'timeInForce': 'FOK'}`).",
    tags={"trading", "order", "spot", "private"}
)
async def create_spot_limit_order_tool(
    exchange_id: Annotated[str, Field(description="The ID of the exchange (e.g., 'binance', 'kucoin'). Case-insensitive.")],
    symbol: Annotated[str, Field(description="The trading symbol for the order (e.g., 'BTC/USDT', 'ETH/USDT').")] = 'BTC/USDT',
    side: Annotated[str, Field(description="The order side: 'buy' or 'sell'.")] = 'buy',
    amount: Annotated[float, Field(description="The amount of the asset to buy or sell. Must be greater than 0.", gt=0)],
    price: Annotated[float, Field(description="The price for the limit order. Must be greater than 0.", gt=0)],
    api_key: Annotated[Optional[str], Field(description="Optional: Your API key for the exchange. If not directly provided, the system may use pre-configured credentials. Authentication is required for this operation.")] = None,
    secret_key: Annotated[Optional[str], Field(description="Optional: Your secret key for the exchange. Used with an API key if authentication is performed (whether keys are provided directly or pre-configured).")] = None,
    passphrase: Annotated[Optional[str], Field(description="Optional: Your API passphrase, if required by the exchange (e.g., for KuCoin, OKX). Used with an API key if authentication is performed and the exchange requires it (whether keys are provided directly or pre-configured).")] = None,
    params: Annotated[Optional[Dict], Field(description="Optional: Extra parameters for the CCXT `createOrder` call or for client instantiation. "
                                                        "Use this for exchange-specific order parameters like `clientOrderId`, `postOnly`, `timeInForce`, etc. "
                                                        "Example: `{'clientOrderId': 'my-order-123', 'timeInForce': 'GTC'}`.")] = None
) -> Dict:
    """Internal use: Creates spot limit order. Primary description is in @mcp.tool decorator."""
    if not api_key or not secret_key:
        return {"error": "API key and secret key are required for create_spot_limit_order."}
        
    tool_params = params.copy() if params else {}
    api_key_info_dict = {'apiKey': api_key, 'secret': secret_key}
    if passphrase:
        api_key_info_dict['password'] = passphrase
        
    client_config_options = tool_params.pop('options', None)
    exchange : ccxtasync.Exchange = None
    try:
        exchange = await get_exchange_instance(exchange_id, api_key_info=api_key_info_dict, exchange_config_options=client_config_options)
        if not exchange.has['createOrder']:
            return {"error": f"Exchange '{exchange_id}' does not support createOrder."}
        
        order = await exchange.createOrder(symbol, 'limit', side, amount, price, params=tool_params)
        return order
    except CCXT_GENERAL_EXCEPTIONS as e:
        return {"error": str(e)}
    except ccxtasync.NotSupported as e:
        return {"error": f"Operation Not Supported: {str(e)}"}
    except Exception as e:
        return {"error": f"An unexpected error occurred in create_spot_limit_order: {str(e)}"}
    finally:
        if exchange:
            await exchange.close()

@mcp.tool(
    name="create_spot_market_order",
    description="Places a new market order in the spot market, to be filled at the best available current price. "
                "API authentication (api_key, secret_key) and trading permissions on the API key are handled externally. "
                "Use `params` for exchange-specific order parameters like `clientOrderId` or quote order quantity (if supported).",
    tags={"trading", "order", "spot", "private"}
)
async def create_spot_market_order_tool(
    exchange_id: Annotated[str, Field(description="The ID of the exchange (e.g., 'binance', 'kucoin'). Case-insensitive.")],
    symbol: Annotated[str, Field(description="The trading symbol for the order (e.g., 'BTC/USDT', 'ETH/USDT').")] = 'BTC/USDT',
    side: Annotated[str, Field(description="The order side: 'buy' or 'sell'.")] = 'buy',
    amount: Annotated[float, Field(description="The amount of the asset to buy or sell. Must be greater than 0.", gt=0)],
    api_key: Annotated[Optional[str], Field(description="Optional: Your API key for the exchange. If not directly provided, the system may use pre-configured credentials. Authentication is required for this operation.")] = None,
    secret_key: Annotated[Optional[str], Field(description="Optional: Your secret key for the exchange. Used with an API key if authentication is performed (whether keys are provided directly or pre-configured).")] = None,
    passphrase: Annotated[Optional[str], Field(description="Optional: Your API passphrase, if required by the exchange (e.g., for KuCoin, OKX). Used with an API key if authentication is performed and the exchange requires it (whether keys are provided directly or pre-configured).")] = None,
    params: Annotated[Optional[Dict], Field(description="Optional: Extra parameters for the CCXT `createOrder` call or for client instantiation. "
                                                        "Use this for exchange-specific order parameters like `clientOrderId`, `quoteOrderQty`, etc. "
                                                        "Example: `{'clientOrderId': 'my-order-123', 'quoteOrderQty': '100'}`.")] = None
) -> Dict:
    """Internal use: Creates spot market order. Primary description is in @mcp.tool decorator."""
    if not api_key or not secret_key:
        return {"error": "API key and secret key are required for create_spot_market_order."}
        
    tool_params = params.copy() if params else {}
    api_key_info_dict = {'apiKey': api_key, 'secret': secret_key}
    if passphrase:
        api_key_info_dict['password'] = passphrase
        
    client_config_options = tool_params.pop('options', None)
    exchange : ccxtasync.Exchange = None
    try:
        exchange = await get_exchange_instance(exchange_id, api_key_info=api_key_info_dict, exchange_config_options=client_config_options)
        if not exchange.has['createOrder']:
            return {"error": f"Exchange '{exchange_id}' does not support createOrder."}
        
        order = await exchange.createOrder(symbol, 'market', side, amount, params=tool_params)
        return order
    except CCXT_GENERAL_EXCEPTIONS as e:
        return {"error": str(e)}
    except ccxtasync.NotSupported as e:
        return {"error": f"Operation Not Supported: {str(e)}"}
    except Exception as e:
        return {"error": f"An unexpected error occurred in create_spot_market_order: {str(e)}"}
    finally:
        if exchange:
            await exchange.close()

@mcp.tool(
    name="create_futures_limit_order",
    description="Places a new limit order in a futures/swap market. "
                "API authentication (api_key, secret_key) and trading permissions are handled externally. "
                "CRITICAL: The CCXT client MUST be initialized for the correct market type (e.g., 'future', 'swap') using `params` "
                "(e.g., `{'options': {'defaultType': 'future'}}`). "
                "Use `params` also for exchange-specific order parameters like `clientOrderId`, `postOnly`, `reduceOnly`, `timeInForce`.",
    tags={"trading", "order", "futures", "private"}
)
async def create_futures_limit_order_tool(
    exchange_id: Annotated[str, Field(description="The ID of the exchange (e.g., 'binance', 'bybit'). Case-insensitive.")],
    symbol: Annotated[str, Field(description="The trading symbol for the order (e.g., 'BTC/USDT:USDT', 'ETH/USDT:USDT').")] = 'BTC/USDT:USDT',
    side: Annotated[str, Field(description="The order side: 'buy' or 'sell'.")] = 'buy',
    amount: Annotated[float, Field(description="The amount of the asset to buy or sell. Must be greater than 0.", gt=0)],
    price: Annotated[float, Field(description="The price for the limit order. Must be greater than 0.", gt=0)],
    api_key: Annotated[Optional[str], Field(description="Optional: Your API key for the exchange. If not directly provided, the system may use pre-configured credentials. Authentication is required for this operation.")] = None,
    secret_key: Annotated[Optional[str], Field(description="Optional: Your secret key for the exchange. Used with an API key if authentication is performed (whether keys are provided directly or pre-configured).")] = None,
    passphrase: Annotated[Optional[str], Field(description="Optional: Your API passphrase, if required by the exchange (e.g., for KuCoin, OKX). Used with an API key if authentication is performed and the exchange requires it (whether keys are provided directly or pre-configured).")] = None,
    params: Annotated[Optional[Dict], Field(description="Optional: Extra parameters for the CCXT `createOrder` call or for client instantiation. "
                                                        "Use this to specify market types (e.g., `{'options': {'defaultType': 'future'}}`) "
                                                        "and for exchange-specific order parameters like `clientOrderId`, `postOnly`, `reduceOnly`, `timeInForce`. "
                                                        "Example: `{'options': {'defaultType': 'future'}, 'reduceOnly': true, 'timeInForce': 'GTC'}`.")] = None
) -> Dict:
    """Internal use: Creates futures limit order. Primary description is in @mcp.tool decorator."""
    if not api_key or not secret_key:
        return {"error": "API key and secret key are required for create_futures_limit_order."}
        
    tool_params = params.copy() if params else {}
    api_key_info_dict = {'apiKey': api_key, 'secret': secret_key}
    if passphrase:
        api_key_info_dict['password'] = passphrase
        
    client_config_options = tool_params.pop('options', None)
    exchange : ccxtasync.Exchange = None
    try:
        exchange = await get_exchange_instance(exchange_id, api_key_info=api_key_info_dict, exchange_config_options=client_config_options)
        if not exchange.has['createOrder']:
            return {"error": f"Exchange '{exchange_id}' does not support createOrder."}
        
        order = await exchange.createOrder(symbol, 'limit', side, amount, price, params=tool_params)
        return order
    except CCXT_GENERAL_EXCEPTIONS as e:
        return {"error": str(e)}
    except ccxtasync.NotSupported as e:
        return {"error": f"Operation Not Supported: {str(e)}"}
    except Exception as e:
        return {"error": f"An unexpected error occurred in create_futures_limit_order: {str(e)}"}
    finally:
        if exchange:
            await exchange.close()

@mcp.tool(
    name="create_futures_market_order",
    description="Places a new market order in a futures/swap market, filled at the best available current price. "
                "API authentication (api_key, secret_key) and trading permissions are handled externally. "
                "CRITICAL: The CCXT client MUST be initialized for the correct market type (e.g., 'future', 'swap') using `params` "
                "(e.g., `{'options': {'defaultType': 'future'}}`). "
                "Use `params` also for exchange-specific parameters like `clientOrderId` or `reduceOnly`.",
    tags={"trading", "order", "futures", "private"}
)
async def create_futures_market_order_tool(
    exchange_id: Annotated[str, Field(description="The ID of the exchange (e.g., 'binance', 'bybit'). Case-insensitive.")],
    symbol: Annotated[str, Field(description="The trading symbol for the order (e.g., 'BTC/USDT:USDT', 'ETH/USDT:USDT').")] = 'BTC/USDT:USDT',
    side: Annotated[str, Field(description="The order side: 'buy' or 'sell'.")] = 'buy',
    amount: Annotated[float, Field(description="The amount of the asset to buy or sell. Must be greater than 0.", gt=0)],
    api_key: Annotated[Optional[str], Field(description="Optional: Your API key for the exchange. If not directly provided, the system may use pre-configured credentials. Authentication is required for this operation.")] = None,
    secret_key: Annotated[Optional[str], Field(description="Optional: Your secret key for the exchange. Used with an API key if authentication is performed (whether keys are provided directly or pre-configured).")] = None,
    passphrase: Annotated[Optional[str], Field(description="Optional: Your API passphrase, if required by the exchange (e.g., for KuCoin, OKX). Used with an API key if authentication is performed and the exchange requires it (whether keys are provided directly or pre-configured).")] = None,
    params: Annotated[Optional[Dict], Field(description="Optional: Extra parameters for the CCXT `createOrder` call or for client instantiation. "
                                                        "Use this to specify market types (e.g., `{'options': {'defaultType': 'future'}}`) "
                                                        "and for exchange-specific parameters like `clientOrderId`, `reduceOnly`. "
                                                        "Example: `{'options': {'defaultType': 'future'}, 'reduceOnly': true}`.")] = None
) -> Dict:
    """Internal use: Creates futures market order. Primary description is in @mcp.tool decorator."""
    if not api_key or not secret_key:
        return {"error": "API key and secret key are required for create_futures_market_order."}
        
    tool_params = params.copy() if params else {}
    api_key_info_dict = {'apiKey': api_key, 'secret': secret_key}
    if passphrase:
        api_key_info_dict['password'] = passphrase
        
    client_config_options = tool_params.pop('options', None)
    exchange : ccxtasync.Exchange = None
    try:
        exchange = await get_exchange_instance(exchange_id, api_key_info=api_key_info_dict, exchange_config_options=client_config_options)
        if not exchange.has['createOrder']:
            return {"error": f"Exchange '{exchange_id}' does not support createOrder."}
        
        order = await exchange.createOrder(symbol, 'market', side, amount, params=tool_params)
        return order
    except CCXT_GENERAL_EXCEPTIONS as e:
        return {"error": str(e)}
    except ccxtasync.NotSupported as e:
        return {"error": f"Operation Not Supported: {str(e)}"}
    except Exception as e:
        return {"error": f"An unexpected error occurred in create_futures_market_order: {str(e)}"}
    finally:
        if exchange:
            await exchange.close()

@mcp.tool(
    name="cancel_order",
    description="Cancels an existing open order on an exchange. "
                "API authentication (api_key, secret_key) is handled externally. "
                "The `symbol` parameter is required by some exchanges, optional for others. "
                "If canceling an order in a non-spot market (futures, options), ensure the CCXT client is initialized correctly using `params` "
                "(e.g., `{'options': {'defaultType': 'future'}}`).",
    tags={"trading", "order", "private"}
)
async def cancel_order_tool(
    exchange_id: Annotated[str, Field(description="The ID of the exchange (e.g., 'binance', 'kucoin'). Case-insensitive.")],
    id: Annotated[str, Field(description="The order ID to cancel.")],
    api_key: Annotated[Optional[str], Field(description="Optional: Your API key for the exchange. If not directly provided, the system may use pre-configured credentials. Authentication is required for this operation.")] = None,
    secret_key: Annotated[Optional[str], Field(description="Optional: Your secret key for the exchange. Used with an API key if authentication is performed (whether keys are provided directly or pre-configured).")] = None,
    symbol: Annotated[Optional[str], Field(description="Optional: The trading symbol for the order (e.g., 'BTC/USDT', 'ETH/USDT'). Some exchanges require this.")],
    passphrase: Annotated[Optional[str], Field(description="Optional: Your API passphrase, if required by the exchange (e.g., for KuCoin, OKX). Used with an API key if authentication is performed and the exchange requires it (whether keys are provided directly or pre-configured).")] = None,
    params: Annotated[Optional[Dict], Field(description="Optional: Extra parameters for the CCXT `cancelOrder` call or for client instantiation. "
                                                        "Use this to specify market types (e.g., `{'options': {'defaultType': 'future'}}`), "
                                                        "or pass other exchange-specific arguments.")] = None
) -> Dict:
    """Internal use: Cancels order. Primary description is in @mcp.tool decorator."""
    if not api_key or not secret_key:
        return {"error": "API key and secret key are required for cancel_order."}
        
    tool_params = params.copy() if params else {}
    api_key_info_dict = {'apiKey': api_key, 'secret': secret_key}
    if passphrase:
        api_key_info_dict['password'] = passphrase
        
    client_config_options = tool_params.pop('options', None)
    exchange : ccxtasync.Exchange = None
    try:
        exchange = await get_exchange_instance(exchange_id, api_key_info=api_key_info_dict, exchange_config_options=client_config_options)
        if not exchange.has['cancelOrder']:
            return {"error": f"Exchange '{exchange_id}' does not support cancelOrder."}
        
        result = await exchange.cancelOrder(id, symbol, params=tool_params)
        return result
    except CCXT_GENERAL_EXCEPTIONS as e:
        return {"error": str(e)}
    except ccxtasync.NotSupported as e:
        return {"error": f"Operation Not Supported: {str(e)}"}
    except Exception as e:
        return {"error": f"An unexpected error occurred in cancel_order: {str(e)}"}
    finally:
        if exchange:
            await exchange.close()

@mcp.tool(
    name="fetch_order_history",
    description="Fetches a list of your orders (open, closed, canceled, etc.) for an account, optionally filtered by symbol, time, and limit. "
                "API authentication (api_key, secret_key) is handled externally. "
                "If fetching orders from a non-spot market (futures, options), ensure the CCXT client is initialized correctly using `params` "
                "(e.g., `{'options': {'defaultType': 'future'}}`). "
                "Some exchanges might use `fetchOrders` to get only open or closed orders by default; use `params` for finer control if supported (e.g. `{'status': 'open'}`).",
    tags={"trading", "orders", "history", "private"}
)
async def fetch_orders_tool(
    exchange_id: Annotated[str, Field(description="The ID of the exchange (e.g., 'binance', 'kucoin'). Case-insensitive.")],
    api_key: Annotated[Optional[str], Field(description="Optional: Your API key for the exchange. If not directly provided, the system may use pre-configured credentials. Authentication is required for this operation.")] = None,
    secret_key: Annotated[Optional[str], Field(description="Optional: Your secret key for the exchange. Used with an API key if authentication is performed (whether keys are provided directly or pre-configured).")] = None,
    symbol: Annotated[Optional[str], Field(description="Optional: The trading symbol to filter orders by (e.g., 'BTC/USDT', 'ETH/USDT').")] = None,
    since: Annotated[Optional[int], Field(description="Optional: The timestamp in milliseconds for the start of the range to fetch from.")] = None,
    limit: Annotated[Optional[int], Field(description="Optional: The number of orders to fetch. Default is 100; max depends on exchange.")] = 100,
    passphrase: Annotated[Optional[str], Field(description="Optional: Your API passphrase, if required by the exchange (e.g., for KuCoin, OKX). Used with an API key if authentication is performed and the exchange requires it (whether keys are provided directly or pre-configured).")] = None,
    params: Annotated[Optional[Dict], Field(description="Optional: Extra parameters for the CCXT `fetchOrders` call or for client instantiation. "
                                                        "Use this to specify market types (e.g., `{'options': {'defaultType': 'future'}}`), "
                                                        "or pass other exchange-specific arguments. "
                                                        "Example: `{'status': 'open'}` to fetch only open orders.")] = None
) -> Dict:
    """Internal use: Fetches order history. Primary description is in @mcp.tool decorator."""
    if not api_key or not secret_key:
        return {"error": "API key and secret key are required for fetch_order_history."}
        
    tool_params = params.copy() if params else {}
    api_key_info_dict = {'apiKey': api_key, 'secret': secret_key}
    if passphrase:
        api_key_info_dict['password'] = passphrase
        
    client_config_options = tool_params.pop('options', None)
    exchange : ccxtasync.Exchange = None
    try:
        exchange = await get_exchange_instance(exchange_id, api_key_info=api_key_info_dict, exchange_config_options=client_config_options)
        if not exchange.has['fetchOrders']:
            return {"error": f"Exchange '{exchange_id}' does not support fetchOrders."}
        
        orders = await exchange.fetchOrders(symbol, since, limit, params=tool_params)
        return orders
    except CCXT_GENERAL_EXCEPTIONS as e:
        return {"error": str(e)}
    except ccxtasync.NotSupported as e:
        return {"error": f"Operation Not Supported: {str(e)}"}
    except Exception as e:
        return {"error": f"An unexpected error occurred in fetch_order_history: {str(e)}"}
    finally:
        if exchange:
            await exchange.close()

@mcp.tool(
    name="fetch_my_trade_history",
    description="Fetches the history of your executed trades (fills) for an account, optionally filtered by symbol, time, and limit. "
                "API authentication (api_key, secret_key) is handled externally. "
                "If fetching trades from a non-spot market (futures, options), ensure the CCXT client is initialized correctly using `params` "
                "(e.g., `{'options': {'defaultType': 'future'}}`). "
                "Use `params` for any exchange-specific filtering not covered by standard arguments (e.g. filtering by orderId).",
    tags={"trading", "trades", "history", "private"}
)
async def fetch_my_trades_tool(
    exchange_id: Annotated[str, Field(description="The ID of the exchange (e.g., 'binance', 'kucoin'). Case-insensitive.")],
    api_key: Annotated[Optional[str], Field(description="Optional: Your API key for the exchange. If not directly provided, the system may use pre-configured credentials. Authentication is required for this operation.")] = None,
    secret_key: Annotated[Optional[str], Field(description="Optional: Your secret key for the exchange. Used with an API key if authentication is performed (whether keys are provided directly or pre-configured).")] = None,
    symbol: Annotated[Optional[str], Field(description="Optional: The trading symbol to filter trades by (e.g., 'BTC/USDT', 'ETH/USDT').")] = None,
    since: Annotated[Optional[int], Field(description="Optional: The timestamp in milliseconds for the start of the range to fetch from.")] = None,
    limit: Annotated[Optional[int], Field(description="Optional: The number of trades to fetch. Default is 100; max depends on exchange.")] = 100,
    passphrase: Annotated[Optional[str], Field(description="Optional: Your API passphrase, if required by the exchange (e.g., for KuCoin, OKX). Used with an API key if authentication is performed and the exchange requires it (whether keys are provided directly or pre-configured).")] = None,
    params: Annotated[Optional[Dict], Field(description="Optional: Extra parameters for the CCXT `fetchMyTrades` call or for client instantiation. "
                                                        "Use this to specify market types (e.g., `{'options': {'defaultType': 'future'}}`), "
                                                        "or pass other exchange-specific arguments. "
                                                        "Example: `{'orderId': '123456'}` to filter by order ID.")] = None
) -> Dict:
    """Internal use: Fetches trade history. Primary description is in @mcp.tool decorator."""
    if not api_key or not secret_key:
        return {"error": "API key and secret key are required for fetch_my_trade_history."}
        
    tool_params = params.copy() if params else {}
    api_key_info_dict = {'apiKey': api_key, 'secret': secret_key}
    if passphrase:
        api_key_info_dict['password'] = passphrase
        
    client_config_options = tool_params.pop('options', None)
    exchange : ccxtasync.Exchange = None
    try:
        exchange = await get_exchange_instance(exchange_id, api_key_info=api_key_info_dict, exchange_config_options=client_config_options)
        if not exchange.has['fetchMyTrades']:
            return {"error": f"Exchange '{exchange_id}' does not support fetchMyTrades."}
        
        trades = await exchange.fetchMyTrades(symbol, since, limit, params=tool_params)
        return trades
    except CCXT_GENERAL_EXCEPTIONS as e:
        return {"error": str(e)}
    except ccxtasync.NotSupported as e:
        return {"error": f"Operation Not Supported: {str(e)}"}
    except Exception as e:
        return {"error": f"An unexpected error occurred in fetch_my_trade_history: {str(e)}"}
    finally:
        if exchange:
            await exchange.close()

@mcp.tool(
    name="calculate_technical_indicator",
    description="Fetches OHLCV data for a given symbol and timeframe, then calculates a specified technical indicator (e.g., RSI, SMA, EMA, MACD, Bollinger Bands, Stochastic Oscillator, ATR). "
                "Returns a time series of calculated indicator values. "
                "The number of data points returned corresponds to the OHLCV data fetched (controlled by 'ohlcv_limit' in indicator_params).",
    tags={"analysis", "indicators", "public"}
)
async def calculate_technical_indicator_tool(
    exchange_id: Annotated[str, Field(description="The ID of the exchange (e.g., 'binance', 'kucoin'). Case-insensitive.")],
    symbol: Annotated[str, Field(description="The trading symbol to fetch OHLCV data for (e.g., 'BTC/USDT', 'ETH/USDT').")] = 'BTC/USDT',
    timeframe: Annotated[TimeframeLiteral, Field(description="The timeframe for the candlesticks (e.g., '1m', '5m', '1h', '1d').")] = '1d',
    indicator_name: Annotated[str, Field(description="The name of the technical indicator to calculate (e.g., 'rsi', 'sma', 'ema', 'macd', 'bbands', 'stoch', 'atr').")] = 'rsi',
    ohlcv_limit: Annotated[int, Field(description="The number of OHLCV candles to fetch for indicator calculation. More data = more accurate indicators.", ge=20)] = 100,
    indicator_params: Annotated[Optional[Dict], Field(description="Optional: Parameters specific to the indicator (e.g., {'length': 14} for RSI). "
                                                        "If not provided, defaults will be used: "
                                                        "RSI/SMA/EMA: {'length': 14 or 20}, "
                                                        "MACD: {'fast_length': 12, 'slow_length': 26, 'signal_length': 9}, "
                                                        "BBANDS: {'length': 20, 'std_dev': 2.0}, "
                                                        "STOCH: {'k_period': 14, 'd_period': 3, 'smooth_k': 3}, "
                                                        "ATR: {'period': 14}.")] = None,
    api_key: Annotated[Optional[str], Field(description="Optional: Your API key for the exchange. If not directly provided, the system may use pre-configured credentials. May provide more data or higher rate limits.")] = None,
    secret_key: Annotated[Optional[str], Field(description="Optional: Your secret key for the exchange. Used with an API key if authentication is performed (whether keys are provided directly or pre-configured).")] = None,
    passphrase: Annotated[Optional[str], Field(description="Optional: Your API passphrase, if required by the exchange (e.g., for KuCoin, OKX). Used with an API key if authentication is performed and the exchange requires it (whether keys are provided directly or pre-configured).")] = None,
    params: Annotated[Optional[Dict], Field(description="Optional: Extra parameters for the CCXT `fetchOHLCV` call or for client instantiation. "
                                                        "Use this to specify market types (e.g., `{'options': {'defaultType': 'future'}}`), "
                                                        "or pass other exchange-specific arguments.")] = None
) -> Dict:
    """Internal use: Calculates technical indicator. Primary description is in @mcp.tool decorator."""
    # Set up API credentials if provided
    api_key_info_dict = {}
    if api_key:
        api_key_info_dict['apiKey'] = api_key
    if secret_key:
        api_key_info_dict['secret'] = secret_key
    if passphrase:
        api_key_info_dict['password'] = passphrase
    
    # Parse indicator parameters with defaults
    params_dict = indicator_params.copy() if indicator_params else {}
    
    # Defaults based on indicator_name
    defaults = {
        'rsi': {'length': 14, 'price_source': 'close'},
        'sma': {'length': 20, 'price_source': 'close'},
        'ema': {'length': 20, 'price_source': 'close'},
        'macd': {'fast_length': 12, 'slow_length': 26, 'signal_length': 9, 'price_source': 'close'},
        'bbands': {'length': 20, 'std_dev': 2.0, 'price_source': 'close'},
        'stoch': {'k_period': 14, 'd_period': 3, 'smooth_k': 3},
        'atr': {'period': 14}
    }
    
    if indicator_name.lower() in defaults:
        for key, default_value in defaults[indicator_name.lower()].items():
            params_dict.setdefault(key, default_value)
    
    client_config_options = params.pop('options', None) if params else None
    exchange : ccxtasync.Exchange = None
    try:
        exchange = await get_exchange_instance(exchange_id, api_key_info=api_key_info_dict or None, exchange_config_options=client_config_options)
        if not exchange.has['fetchOHLCV']:
            return {"error": f"Exchange '{exchange_id}' does not support fetchOHLCV."}
        
        ohlcv = await exchange.fetchOHLCV(symbol, timeframe, None, ohlcv_limit, params=params or {})
        df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
        df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
        df.set_index('timestamp', inplace=True)
        
        # Calculate indicator based on name
        indicator_name_lower = indicator_name.lower()
        result = None
        
        if indicator_name_lower == 'rsi':
            result = compute_rsi(df, length=params_dict.get('length', 14), price_source=params_dict.get('price_source', 'close'))
        elif indicator_name_lower == 'sma':
            result = compute_sma(df, length=params_dict.get('length', 20), price_source=params_dict.get('price_source', 'close'))
        elif indicator_name_lower == 'ema':
            result = compute_ema(df, length=params_dict.get('length', 20), price_source=params_dict.get('price_source', 'close'))
        elif indicator_name_lower == 'macd':
            result = compute_macd(
                df, 
                fast_length=params_dict.get('fast_length', 12), 
                slow_length=params_dict.get('slow_length', 26), 
                signal_length=params_dict.get('signal_length', 9),
                price_source=params_dict.get('price_source', 'close')
            )
        elif indicator_name_lower == 'bbands':
            result = compute_bbands(
                df, 
                length=params_dict.get('length', 20), 
                std_dev=params_dict.get('std_dev', 2.0),
                price_source=params_dict.get('price_source', 'close')
            )
        elif indicator_name_lower == 'stoch':
            result = compute_stochastic_oscillator(
                df, 
                k_period=params_dict.get('k_period', 14), 
                d_period=params_dict.get('d_period', 3), 
                smooth_k=params_dict.get('smooth_k', 3)
            )
        elif indicator_name_lower == 'atr':
            result = compute_atr(
                df, 
                period=params_dict.get('period', 14)
            )
        else:
            return {"error": f"Unsupported indicator: {indicator_name}"}
        
        if result is None:
            return {"error": f"Failed to calculate {indicator_name} for {symbol}"}
        
        # Convert result to JSON-serializable format
        if isinstance(result, tuple):
            return {
                'indicator': indicator_name,
                'symbol': symbol,
                'timeframe': timeframe,
                'data': [series.dropna().to_dict() for series in result]
            }
        else:
            return {
                'indicator': indicator_name,
                'symbol': symbol,
                'timeframe': timeframe,
                'data': result.dropna().to_dict()
            }
    except CCXT_GENERAL_EXCEPTIONS as e:
        return {"error": str(e)}
    except ccxtasync.NotSupported as e:
        return {"error": f"Operation Not Supported: {str(e)}"}
    except Exception as e:
        return {"error": f"An unexpected error occurred in calculate_technical_indicator: {str(e)}"}
    finally:
        if exchange:
            await exchange.close()

# --- Main execution (for running the server) ---
if __name__ == "__main__":
    import sys
    if len(sys.argv) > 1 and sys.argv[1] == "--stdio":
        print("Starting CCXT MCP Server (stdio mode)...")
        import asyncio
        asyncio.run(mcp.run_stdio_async())
    else:
        print("Starting CCXT MCP Server (Async with Annotated Params and Tool Metadata)...")
        mcp.run()