"""FastMCP server for akshare-one-derivative.

Exposes all public akshare_one APIs as MCP tools.

Serialization: DataFrame → list[dict] (records); NaN/NaT → None; Timestamp → ISO8601; ±inf → None.
Transport: stdio only.
"""

from __future__ import annotations

import datetime as _dt
import json
import math
from collections.abc import Callable
from functools import wraps
from typing import Annotated, Any, Literal, TypeVar, cast

import pandas as pd
from fastmcp import FastMCP
from fastmcp.exceptions import ToolError
from pydantic import Field

import akshare_one as _api

mcp = FastMCP(
    "akshare-one-derivative",
    instructions=(
        "akshare_one 公共 API（get_*）与衍生指标工具（compute_*/calculate_*）的 MCP 工具封装；"
        "DataFrame 输出统一序列化为 records (list[dict])，时间字段为 ISO8601 字符串。"
        "compute_* 工具不接受 DataFrame 输入，使用 symbol/date 等简单参数在服务端自动取数并计算。"
    ),
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _default_json_handler(value: Any) -> str:
    """Fallback handler for ``DataFrame.to_json``.

    Args:
        value: 任意单元格对象。

    Returns:
        JSON 可序列化的字符串表示。
    """
    if isinstance(value, (bytes, bytearray)):
        return bytes(value).decode("utf-8", errors="replace")
    if isinstance(value, (_dt.datetime, _dt.date, _dt.time)):
        return value.isoformat()
    isoformat = getattr(value, "isoformat", None)
    if callable(isoformat):
        try:
            return str(isoformat())
        except Exception:
            return str(value)
    return str(value)


def _serialize_dataframe(
    df: pd.DataFrame | None,
) -> list[dict[str, Any]]:
    """Serialize a pandas DataFrame into JSON-safe records.

    Args:
        df: pandas DataFrame 或 None。

    Returns:
        list[dict[str, Any]]: records 形式的数据。

    Raises:
        ToolError: 序列化失败时抛出。
    """
    if df is None or (isinstance(df, pd.DataFrame) and df.empty):
        return []
    if not isinstance(df, pd.DataFrame):
        raise ToolError("Internal error: expected DataFrame")

    safe_df = df.replace([math.inf, -math.inf], None)
    try:
        payload = safe_df.to_json(
            orient="records",
            date_format="iso",
            default_handler=_default_json_handler,
        )
        data = json.loads(payload)
        if not isinstance(data, list):
            raise ToolError("Internal error: unexpected serialization result")
        return cast(list[dict[str, Any]], data)
    except ToolError:
        raise
    except Exception as e:
        raise ToolError("Internal error: DataFrame serialization failed") from e


F = TypeVar("F", bound=Callable[..., Any])


def _tool_error_boundary(fn: F) -> F:
    """Decorator: convert common exceptions into ToolError.

    Args:
        fn: 被包装的工具函数。

    Returns:
        包装后的函数。

    Raises:
        ToolError: 当内部抛出异常时统一转换。
    """

    @wraps(fn)
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        try:
            return fn(*args, **kwargs)
        except ToolError:
            raise
        except (ValueError, TypeError, NotImplementedError) as e:
            raise ToolError(str(e)) from e
        except Exception as e:
            raise ToolError("Internal error") from e

    return cast(F, wrapper)


# ---------------------------------------------------------------------------
# Symbol listing
# ---------------------------------------------------------------------------

_COMMODITY_OPTIONS_SYMBOLS: dict[str, list[str]] = {
    "shfe": [
        "铜期权",
        "铝期权",
        "锌期权",
        "铅期权",
        "镍期权",
        "锡期权",
        "黄金期权",
        "白银期权",
        "天然橡胶期权",
        "20号胶期权",
        "丁二烯橡胶期权",
        "燃料油期权",
        "低硫燃料油期权",
        "石油沥青期权",
        "螺纹钢期权",
        "热轧卷板期权",
        "不锈钢期权",
        "纸浆期权",
        "氧化铝期权",
    ],
    "dce": [
        "玉米期权",
        "豆粕期权",
        "铁矿石期权",
        "液化石油气期权",
        "聚乙烯期权",
        "聚氯乙烯期权",
        "聚丙烯期权",
        "棕榈油期权",
        "黄大豆1号期权",
        "黄大豆2号期权",
        "豆油期权",
        "乙二醇期权",
        "苯乙烯期权",
        "鸡蛋期权",
        "玉米淀粉期权",
        "生猪期权",
        "原木期权",
    ],
    "czce": [
        "白糖期权",
        "棉花期权",
        "甲醇期权",
        "PTA期权",
        "动力煤期权",
        "菜籽粕期权",
        "菜籽油期权",
        "花生期权",
        "对二甲苯期权",
        "烧碱期权",
        "纯碱期权",
        "短纤期权",
        "锰硅期权",
        "硅铁期权",
        "尿素期权",
        "苹果期权",
        "红枣期权",
        "玻璃期权",
        "瓶片期权",
    ],
    "gfex": ["工业硅", "碳酸锂"],
}

_OPTIONS_UNDERLYINGS: list[dict[str, str]] = [
    {"symbol": "510050", "name": "50ETF"},
    {"symbol": "510300", "name": "300ETF"},
    {"symbol": "510500", "name": "500ETF"},
    {"symbol": "159901", "name": "100ETF"},
    {"symbol": "159919", "name": "300ETF(深)"},
    {"symbol": "588000", "name": "科创50ETF"},
    {"symbol": "588080", "name": "科创板50ETF"},
]


@mcp.tool
@_tool_error_boundary
def list_symbols(
    category: Annotated[
        Literal[
            "futures",
            "futures_inventory",
            "options_underlying",
            "commodity_options",
            "commodity_options_contracts",
        ],
        Field(
            description=(
                "符号类别: "
                "futures=期货品种代码, "
                "futures_inventory=期货库存品种(中文名), "
                "options_underlying=ETF期权标的代码, "
                "commodity_options=商品期权品种名(按交易所), "
                "commodity_options_contracts=商品期权品种名(扁平列表)"
            )
        ),
    ],
) -> list[dict[str, Any]]:
    """查询各类工具可用的 symbol 列表。

    Args:
        category: 符号类别。

    Returns:
        list[dict[str, Any]]: 可用符号列表。

    Raises:
        ToolError: 查询失败时抛出。
    """
    if category == "futures":
        df = _api.get_futures_main_contracts(source="sina")
        return _serialize_dataframe(df)

    if category == "futures_inventory":
        try:
            _api.get_futures_inventory(symbol="__invalid__")
        except ValueError as e:
            import ast
            import re

            match = re.search(r"\{.*\}", str(e))
            if match:
                mapping = ast.literal_eval(match.group())
                return [{"name": k, "code": v} for k, v in mapping.items()]
        raise ToolError("Failed to retrieve futures inventory symbol list")

    if category == "options_underlying":
        return _OPTIONS_UNDERLYINGS  # type: ignore[return-value]

    if category == "commodity_options":
        return [
            {"exchange": ex, "symbols": syms} for ex, syms in _COMMODITY_OPTIONS_SYMBOLS.items()
        ]

    if category == "commodity_options_contracts":
        items: list[dict[str, Any]] = []
        for ex, syms in _COMMODITY_OPTIONS_SYMBOLS.items():
            for sym in syms:
                items.append({"exchange": ex, "commodity_symbol": sym})
        return items

    raise ToolError(f"Unknown category: {category}")


# ---------------------------------------------------------------------------
# Stock tools
# ---------------------------------------------------------------------------


@mcp.tool
@_tool_error_boundary
def get_basic_info(
    symbol: Annotated[str, Field(description="股票代码 (e.g. '600000')")],
    source: Annotated[
        Literal["eastmoney"],
        Field(description="数据源 ('eastmoney')"),
    ] = "eastmoney",
) -> list[dict[str, Any]]:
    """获取股票基础信息。

    Args:
        symbol: 股票代码。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(_api.get_basic_info(symbol=symbol, source=source))


@mcp.tool
@_tool_error_boundary
def get_hist_data(
    symbol: Annotated[str, Field(description="股票代码 (e.g. '600000')")],
    interval: Annotated[
        Literal["minute", "hour", "day", "week", "month", "year"],
        Field(description="K线间隔 (minute/hour/day/week/month/year)"),
    ] = "day",
    interval_multiplier: Annotated[int, Field(description="间隔倍数 (e.g. 5 表示 5 分钟)")] = 1,
    start_date: Annotated[str, Field(description="开始日期 (YYYY-MM-DD)")] = "1970-01-01",
    end_date: Annotated[str, Field(description="结束日期 (YYYY-MM-DD)")] = "2030-12-31",
    adjust: Annotated[
        Literal["none", "qfq", "hfq"],
        Field(description="复权类型 (none/qfq/hfq)"),
    ] = "none",
    source: Annotated[
        Literal["eastmoney", "eastmoney_direct", "sina"],
        Field(description="数据源 (eastmoney/eastmoney_direct/sina)"),
    ] = "eastmoney_direct",
) -> list[dict[str, Any]]:
    """获取股票历史行情数据。

    Args:
        symbol: 股票代码。
        interval: K线间隔。
        interval_multiplier: 间隔倍数。
        start_date: 开始日期。
        end_date: 结束日期。
        adjust: 复权类型。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(
        _api.get_hist_data(
            symbol=symbol,
            interval=interval,
            interval_multiplier=interval_multiplier,
            start_date=start_date,
            end_date=end_date,
            adjust=adjust,
            source=source,
        )
    )


@mcp.tool
@_tool_error_boundary
def get_realtime_data(
    symbol: Annotated[
        str | None,
        Field(description="股票代码，为 None 时返回该数据源支持的全量行情"),
    ] = None,
    source: Annotated[
        Literal["eastmoney", "eastmoney_direct", "xueqiu"],
        Field(description="数据源 (eastmoney/eastmoney_direct/xueqiu)"),
    ] = "eastmoney_direct",
) -> list[dict[str, Any]]:
    """获取股票实时行情数据。

    Args:
        symbol: 股票代码。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(_api.get_realtime_data(symbol=symbol, source=source))


@mcp.tool
@_tool_error_boundary
def get_news_data(
    symbol: Annotated[str, Field(description="股票代码 (如 '300059')")],
    source: Annotated[
        Literal["eastmoney"], Field(description="数据源 ('eastmoney')")
    ] = "eastmoney",
) -> list[dict[str, Any]]:
    """获取个股新闻数据。

    Args:
        symbol: 股票代码。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(_api.get_news_data(symbol=symbol, source=source))


@mcp.tool
@_tool_error_boundary
def get_balance_sheet(
    symbol: Annotated[str, Field(description="股票代码 (如 '600600')")],
    source: Annotated[Literal["sina"], Field(description="数据源 ('sina')")] = "sina",
) -> list[dict[str, Any]]:
    """获取资产负债表数据。

    Args:
        symbol: 股票代码。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(_api.get_balance_sheet(symbol=symbol, source=source))


@mcp.tool
@_tool_error_boundary
def get_income_statement(
    symbol: Annotated[str, Field(description="股票代码 (如 '600600')")],
    source: Annotated[Literal["sina"], Field(description="数据源 ('sina')")] = "sina",
) -> list[dict[str, Any]]:
    """获取利润表数据。

    Args:
        symbol: 股票代码。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(_api.get_income_statement(symbol=symbol, source=source))


@mcp.tool
@_tool_error_boundary
def get_cash_flow(
    symbol: Annotated[str, Field(description="股票代码 (如 '600600')")],
    source: Annotated[Literal["sina"], Field(description="数据源 ('sina')")] = "sina",
) -> list[dict[str, Any]]:
    """获取现金流量表数据。

    Args:
        symbol: 股票代码。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(_api.get_cash_flow(symbol=symbol, source=source))


@mcp.tool
@_tool_error_boundary
def get_financial_metrics(
    symbol: Annotated[str, Field(description="股票代码 (如 '600600')")],
    source: Annotated[
        Literal["eastmoney_direct"],
        Field(description="数据源 ('eastmoney_direct')"),
    ] = "eastmoney_direct",
) -> list[dict[str, Any]]:
    """获取三大财务报表关键指标。

    Args:
        symbol: 股票代码。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(_api.get_financial_metrics(symbol=symbol, source=source))


@mcp.tool
@_tool_error_boundary
def get_inner_trade_data(
    symbol: Annotated[str, Field(description="股票代码 (如 '600000')")],
    source: Annotated[Literal["xueqiu"], Field(description="数据源 ('xueqiu')")] = "xueqiu",
) -> list[dict[str, Any]]:
    """获取雪球内部交易数据。

    Args:
        symbol: 股票代码。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(_api.get_inner_trade_data(symbol=symbol, source=source))


# ---------------------------------------------------------------------------
# Futures tools
# ---------------------------------------------------------------------------


@mcp.tool
@_tool_error_boundary
def get_futures_hist_data(
    symbol: Annotated[str, Field(description="期货代码 (e.g. 'AG0')")],
    contract: Annotated[
        str,
        Field(description="合约代码 ('main'=主力合约，或指定如 '2602')"),
    ] = "main",
    interval: Annotated[
        Literal["minute", "hour", "day", "week", "month"],
        Field(description="K线间隔 (minute/hour/day/week/month)"),
    ] = "day",
    interval_multiplier: Annotated[int, Field(description="间隔倍数 (e.g. 5 表示 5 分钟)")] = 1,
    start_date: Annotated[str, Field(description="开始日期 (YYYY-MM-DD)")] = "1970-01-01",
    end_date: Annotated[str, Field(description="结束日期 (YYYY-MM-DD)")] = "2030-12-31",
    source: Annotated[Literal["sina"], Field(description="数据源 ('sina')")] = "sina",
) -> list[dict[str, Any]]:
    """获取期货历史数据。

    Args:
        symbol: 期货代码。
        contract: 合约代码。
        interval: K线间隔。
        interval_multiplier: 间隔倍数。
        start_date: 开始日期。
        end_date: 结束日期。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(
        _api.get_futures_hist_data(
            symbol=symbol,
            contract=contract,
            interval=interval,
            interval_multiplier=interval_multiplier,
            start_date=start_date,
            end_date=end_date,
            source=source,
        )
    )


@mcp.tool
@_tool_error_boundary
def get_futures_realtime_data(
    symbol: Annotated[
        str | None,
        Field(description="期货代码，为 None 时返回所有期货"),
    ] = None,
    source: Annotated[Literal["sina"], Field(description="数据源 ('sina')")] = "sina",
) -> list[dict[str, Any]]:
    """获取期货实时行情数据。

    Args:
        symbol: 期货代码。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(_api.get_futures_realtime_data(symbol=symbol, source=source))


@mcp.tool
@_tool_error_boundary
def get_futures_main_contracts(
    source: Annotated[Literal["sina"], Field(description="数据源 ('sina')")] = "sina",
) -> list[dict[str, Any]]:
    """获取期货主力合约列表。

    Args:
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 数据获取失败时抛出。
    """
    return _serialize_dataframe(_api.get_futures_main_contracts(source=source))


# ---------------------------------------------------------------------------
# Options tools
# ---------------------------------------------------------------------------


@mcp.tool
@_tool_error_boundary
def get_options_chain(
    underlying_symbol: Annotated[str, Field(description="标的代码 (e.g. '510300')")],
    source: Annotated[Literal["sina"], Field(description="数据源 ('sina')")] = "sina",
) -> list[dict[str, Any]]:
    """获取期权链数据。

    Args:
        underlying_symbol: 标的代码。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(
        _api.get_options_chain(underlying_symbol=underlying_symbol, source=source)
    )


@mcp.tool
@_tool_error_boundary
def get_options_realtime(
    symbol: Annotated[str | None, Field(description="期权代码 (如 '10004005')")] = None,
    underlying_symbol: Annotated[
        str | None,
        Field(description="标的代码，获取该标的所有期权"),
    ] = None,
    source: Annotated[Literal["sina"], Field(description="数据源 ('sina')")] = "sina",
) -> list[dict[str, Any]]:
    """获取期权实时行情数据。必须且只能提供 symbol 或 underlying_symbol 之一。

    Args:
        symbol: 期权代码。
        underlying_symbol: 标的代码。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数互斥校验失败或数据获取失败时抛出。
    """
    if symbol is not None and underlying_symbol is not None:
        raise ToolError("Cannot specify both 'symbol' and 'underlying_symbol'.")
    if symbol is None and underlying_symbol is None:
        raise ToolError("Must specify either 'symbol' or 'underlying_symbol'.")
    return _serialize_dataframe(
        _api.get_options_realtime(
            symbol=symbol,
            underlying_symbol=underlying_symbol,
            source=source,
        )
    )


@mcp.tool
@_tool_error_boundary
def get_options_expirations(
    underlying_symbol: Annotated[str, Field(description="标的代码")],
    source: Annotated[Literal["sina"], Field(description="数据源 ('sina')")] = "sina",
) -> list[str]:
    """获取期权可用到期日列表。

    Args:
        underlying_symbol: 标的代码。
        source: 数据源。

    Returns:
        list[str]: 到期日列表。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _api.get_options_expirations(underlying_symbol=underlying_symbol, source=source)


@mcp.tool
@_tool_error_boundary
def get_options_hist(
    symbol: Annotated[str, Field(description="期权代码")],
    start_date: Annotated[str, Field(description="开始日期 (YYYY-MM-DD)")] = "1970-01-01",
    end_date: Annotated[str, Field(description="结束日期 (YYYY-MM-DD)")] = "2030-12-31",
    source: Annotated[Literal["sina"], Field(description="数据源 ('sina')")] = "sina",
) -> list[dict[str, Any]]:
    """获取期权历史数据。

    Args:
        symbol: 期权代码。
        start_date: 开始日期。
        end_date: 结束日期。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(
        _api.get_options_hist(
            symbol=symbol,
            start_date=start_date,
            end_date=end_date,
            source=source,
        )
    )


# ---------------------------------------------------------------------------
# Options indicators (Track A)
# ---------------------------------------------------------------------------


@mcp.tool
@_tool_error_boundary
def get_options_greeks(
    symbol_or_date: Annotated[str, Field(description="合约代码或交易日（由数据源决定）")],
    source: Annotated[
        Literal["sse", "exchange"],
        Field(description="数据源 ('sse', 'exchange')"),
    ] = "sse",
    exchange: Annotated[
        str | None,
        Field(description="交易所参数（仅 source='exchange' 时有效）"),
    ] = None,
) -> list[dict[str, Any]]:
    """获取期权希腊值数据 (Track A)。

    Args:
        symbol_or_date: 合约代码或交易日。
        source: 数据源。
        exchange: 交易所参数。

    Returns:
        DataFrame records。

    Raises:
        ToolError: source='exchange' 时抛出（当前未实现）；或数据获取失败时抛出。
    """
    if source == "exchange":
        raise ToolError(
            "source='exchange' is not supported for get_options_greeks: "
            "ExchangeOptionsProvider.get_greeks is not implemented. "
            "Use source='sse'."
        )
    if exchange is not None:
        raise ToolError("Parameter 'exchange' is only applicable when source='exchange'.")
    return _serialize_dataframe(
        _api.get_options_greeks(symbol_or_date=symbol_or_date, source=source)
    )


@mcp.tool
@_tool_error_boundary
def get_options_risk_indicators(
    date: Annotated[str, Field(description="交易日 (YYYYMMDD 或 YYYY-MM-DD)")],
    source: Annotated[Literal["sse"], Field(description="数据源 ('sse')")] = "sse",
) -> list[dict[str, Any]]:
    """获取期权风险指标 (Track A)。

    Args:
        date: 交易日。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(_api.get_options_risk_indicators(date=date, source=source))


@mcp.tool
@_tool_error_boundary
def get_options_value_analysis(
    source: Annotated[Literal["sse"], Field(description="数据源 ('sse')")] = "sse",
) -> list[dict[str, Any]]:
    """获取期权价值分析 (Track A)。

    Args:
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 数据获取失败时抛出。
    """
    return _serialize_dataframe(_api.get_options_value_analysis(source=source))


@mcp.tool
@_tool_error_boundary
def get_commodity_options_iv(
    exchange: Annotated[
        Literal["shfe", "dce", "czce", "gfex"],
        Field(description="交易所代码 (shfe/dce/czce/gfex)"),
    ],
    symbol: Annotated[str, Field(description="商品期权品种名称 (如 '铜期权', '铝期权')")],
    date: Annotated[str, Field(description="交易日 (YYYYMMDD 或 YYYY-MM-DD)")],
    source: Annotated[Literal["exchange"], Field(description="数据源 ('exchange')")] = "exchange",
) -> list[dict[str, Any]]:
    """获取商品期权合约系列隐含波动率 (Track A)。

    Args:
        exchange: 交易所代码。
        symbol: 品种名称。
        date: 交易日。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(
        _api.get_commodity_options_iv(
            exchange=exchange,
            symbol=symbol,
            date=date,
            source=source,
        )
    )


# ---------------------------------------------------------------------------
# Futures indicators (Track A)
# ---------------------------------------------------------------------------


@mcp.tool
@_tool_error_boundary
def get_futures_basis(
    date: Annotated[str, Field(description="交易日 (YYYYMMDD 或 YYYY-MM-DD)")],
    symbol: Annotated[str, Field(description="品种代码 (e.g. 'RB', 'AL')")],
    source: Annotated[Literal["akshare"], Field(description="数据源 ('akshare')")] = "akshare",
) -> list[dict[str, Any]]:
    """获取期货基差数据 (Track A)。

    Args:
        date: 交易日。
        symbol: 品种代码。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(_api.get_futures_basis(date=date, symbol=symbol, source=source))


@mcp.tool
@_tool_error_boundary
def get_futures_roll_yield(
    date: Annotated[str, Field(description="交易日 (YYYYMMDD 或 YYYY-MM-DD)")],
    symbol: Annotated[str, Field(description="品种代码 (e.g. 'RB', 'AL')")],
    source: Annotated[Literal["akshare"], Field(description="数据源 ('akshare')")] = "akshare",
) -> list[dict[str, Any]]:
    """获取期货展期收益率 (Track A)。

    Args:
        date: 交易日。
        symbol: 品种代码。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(
        _api.get_futures_roll_yield(date=date, symbol=symbol, source=source)
    )


@mcp.tool
@_tool_error_boundary
def get_futures_position_ranking(
    date: Annotated[str, Field(description="交易日 (YYYYMMDD 或 YYYY-MM-DD)")],
    symbol: Annotated[str, Field(description="品种代码 (e.g. 'RB', 'AL')")],
    exchange: Annotated[
        Literal["shfe", "dce", "czce", "cffex", "gfex"],
        Field(description="交易所代码"),
    ],
    source: Annotated[Literal["akshare"], Field(description="数据源 ('akshare')")] = "akshare",
) -> list[dict[str, Any]]:
    """获取期货持仓排名数据 (Track A)。

    Args:
        date: 交易日。
        symbol: 品种代码。
        exchange: 交易所代码。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(
        _api.get_futures_position_ranking(
            date=date,
            symbol=symbol,
            exchange=exchange,
            source=source,
        )
    )


@mcp.tool
@_tool_error_boundary
def get_futures_warehouse_receipt(
    date: Annotated[str, Field(description="交易日 (YYYYMMDD 或 YYYY-MM-DD)")],
    exchange: Annotated[
        Literal["shfe", "dce", "czce", "gfex"],
        Field(description="交易所代码 (shfe/dce/czce/gfex)"),
    ],
    source: Annotated[Literal["akshare"], Field(description="数据源 ('akshare')")] = "akshare",
) -> list[dict[str, Any]]:
    """获取期货仓单日报 (Track A)。

    Args:
        date: 交易日。
        exchange: 交易所代码。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(
        _api.get_futures_warehouse_receipt(date=date, exchange=exchange, source=source)
    )


@mcp.tool
@_tool_error_boundary
def get_futures_inventory(
    symbol: Annotated[str, Field(description="品种中文名称 (如 '沪铜', '螺纹钢', '沪金')")],
    source: Annotated[Literal["akshare"], Field(description="数据源 ('akshare')")] = "akshare",
) -> list[dict[str, Any]]:
    """获取期货库存数据 (Track A)。

    Args:
        symbol: 品种代码或中文名称。
        source: 数据源。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(_api.get_futures_inventory(symbol=symbol, source=source))


# ---------------------------------------------------------------------------
# Commodity options tools
# ---------------------------------------------------------------------------


@mcp.tool
@_tool_error_boundary
def get_commodity_options_contracts(
    commodity_symbol: Annotated[str, Field(description="商品期权品种名称 (e.g. '黄金期权')")],
) -> list[str]:
    """获取商品期权可用合约月份列表。

    Args:
        commodity_symbol: 商品期权品种名称。

    Returns:
        list[str]: 合约月份/合约代码列表。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _api.get_commodity_options_contracts(commodity_symbol=commodity_symbol, source="sina")


@mcp.tool
@_tool_error_boundary
def get_commodity_options_chain(
    commodity_symbol: Annotated[str, Field(description="商品期权品种名称 (e.g. '黄金期权')")],
    contract: Annotated[
        str,
        Field(description="合约月份/合约代码 (e.g. 'au2604')，传 'main' 自动选主力合约"),
    ] = "main",
) -> list[dict[str, Any]]:
    """获取商品期权当月期权链数据（全量 call/put 合约表）。

    Args:
        commodity_symbol: 商品期权品种名称。
        contract: 合约月份/合约代码。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(
        _api.get_commodity_options_chain(
            commodity_symbol=commodity_symbol, contract=contract, source="sina"
        )
    )


@mcp.tool
@_tool_error_boundary
def get_commodity_options_hist(
    symbol: Annotated[str, Field(description="商品期权合约代码 (e.g. 'au2604C1200')")],
    start_date: Annotated[str, Field(description="开始日期 (YYYY-MM-DD)")] = "1970-01-01",
    end_date: Annotated[str, Field(description="结束日期 (YYYY-MM-DD)")] = "2030-12-31",
) -> list[dict[str, Any]]:
    """获取商品期权历史行情数据（单合约 OHLCV）。

    Args:
        symbol: 商品期权合约代码。
        start_date: 开始日期。
        end_date: 结束日期。

    Returns:
        DataFrame records。

    Raises:
        ToolError: 参数校验或数据获取失败时抛出。
    """
    return _serialize_dataframe(
        _api.get_commodity_options_hist(
            symbol=symbol, start_date=start_date, end_date=end_date, source="sina"
        )
    )


# ---------------------------------------------------------------------------
# Derivatives indicators (Track B) — pure math
# ---------------------------------------------------------------------------


@mcp.tool
@_tool_error_boundary
def calculate_greeks(
    S: Annotated[float, Field(description="标的价格：BS=现货价，Black-76=期货/远期价")],
    K: Annotated[float, Field(description="行权价")],
    T: Annotated[float, Field(description="到期年化时间（单位：年）")],
    r: Annotated[float, Field(description="无风险利率（年化，小数，如 0.02）")],
    sigma: Annotated[float, Field(description="波动率（年化，小数，如 0.2）")],
    option_type: Annotated[
        Literal["call", "put"], Field(description="期权类型 (call/put)")
    ] = "call",
    model: Annotated[
        Literal["black76", "bs"], Field(description="定价模型 (black76/bs)")
    ] = "black76",
) -> list[dict[str, Any]]:
    """计算单个期权希腊值与理论价值（Track B，纯数学）。

    Args:
        S: 标的价格。
        K: 行权价。
        T: 到期年化时间（年）。
        r: 无风险利率（年化，小数）。
        sigma: 波动率（年化，小数）。
        option_type: 期权类型。
        model: 定价模型。

    Returns:
        DataFrame records (single row): delta/gamma/theta/vega/rho/iv/theoretical_value

    Raises:
        ToolError: 参数校验或计算失败时抛出。
    """
    from akshare_one.options_indicators import calculate_greeks as _calc_greeks

    df = _calc_greeks(S=S, K=K, T=T, r=r, sigma=sigma, option_type=option_type, model=model)
    return _serialize_dataframe(df)


@mcp.tool
@_tool_error_boundary
def calculate_iv(
    S: Annotated[float, Field(description="标的价格：BS=现货价，Black-76=期货/远期价")],
    K: Annotated[float, Field(description="行权价")],
    T: Annotated[float, Field(description="到期年化时间（单位：年）")],
    r: Annotated[float, Field(description="无风险利率（年化，小数，如 0.02）")],
    option_price: Annotated[float, Field(description="期权市场价格")],
    option_type: Annotated[
        Literal["call", "put"], Field(description="期权类型 (call/put)")
    ] = "call",
    model: Annotated[
        Literal["black76", "bs"], Field(description="定价模型 (black76/bs)")
    ] = "black76",
) -> float:
    """根据期权价格反解隐含波动率（Track B，纯数学）。

    Args:
        S: 标的价格。
        K: 行权价。
        T: 到期年化时间（年）。
        r: 无风险利率（年化，小数）。
        option_price: 期权市场价格。
        option_type: 期权类型。
        model: 定价模型。

    Returns:
        float: 隐含波动率（年化，小数，如 0.2=20%）。

    Raises:
        ToolError: 参数校验或求解失败时抛出。
    """
    from akshare_one.options_indicators import calculate_iv as _calc_iv

    return _calc_iv(
        S=S,
        K=K,
        T=T,
        r=r,
        option_price=option_price,
        option_type=option_type,
        model=model,
    )


# ---------------------------------------------------------------------------
# Derivatives indicators (Track B) — options self-contained
# ---------------------------------------------------------------------------


@mcp.tool
@_tool_error_boundary
def compute_options_pcr(
    underlying_symbol: Annotated[str, Field(description="ETF期权标的代码 (e.g. '510300')")],
    by: Annotated[
        Literal["volume", "open_interest"],
        Field(description="PCR 聚合口径 (volume/open_interest)"),
    ] = "open_interest",
) -> list[dict[str, Any]]:
    """自包含：获取期权链并计算 PCR (Put-Call Ratio)（Track B）。

    Args:
        underlying_symbol: ETF 期权标的代码。
        by: 统计口径。

    Returns:
        DataFrame records: [pcr, put_value, call_value]

    Raises:
        ToolError: 数据获取或计算失败时抛出。
    """
    from akshare_one.options_indicators import get_options_pcr as _pcr

    chain_df = _api.get_options_chain(underlying_symbol=underlying_symbol, source="sina")
    if chain_df.empty:
        return []
    return _serialize_dataframe(_pcr(chain_df, by=by))


@mcp.tool
@_tool_error_boundary
def compute_options_max_pain(
    underlying_symbol: Annotated[str, Field(description="ETF期权标的代码 (e.g. '510300')")],
) -> list[dict[str, Any]]:
    """自包含：获取期权链并计算 Max Pain（Track B）。

    Args:
        underlying_symbol: ETF 期权标的代码。

    Returns:
        DataFrame records: [max_pain_strike, total_pain]

    Raises:
        ToolError: 数据获取或计算失败时抛出。
    """
    from akshare_one.options_indicators import get_options_max_pain as _max_pain

    chain_df = _api.get_options_chain(underlying_symbol=underlying_symbol, source="sina")
    if chain_df.empty:
        return []
    return _serialize_dataframe(_max_pain(chain_df))


def _merge_chain_with_risk(
    chain_df: pd.DataFrame, risk_df: pd.DataFrame
) -> pd.DataFrame:
    """Merge options chain metadata with SSE risk indicator IV.

    SSE risk_df uses Chinese contract names as ``symbol`` (e.g. '50ETF购2月2850'),
    while chain_df uses numeric codes in ``symbol`` and Chinese names in ``name``.
    This helper joins on the ``name`` column and back-fills IV for OTM contracts
    where SSE returned 0 but a valid market price exists.

    Args:
        chain_df: Chain DataFrame from ``get_options_chain``.
        risk_df: Risk indicator DataFrame from ``get_options_risk_indicators``.

    Returns:
        Merged DataFrame with ``iv`` and ``iv_source`` columns.
    """
    chain_meta = chain_df.drop(columns=["implied_volatility"], errors="ignore")
    merged = chain_meta.merge(
        risk_df[["symbol", "iv"]].rename(columns={"symbol": "name"}),
        on="name",
        how="inner",
    )
    if merged.empty:
        return merged

    spot = _resolve_underlying_price(chain_df)
    if spot is None or spot <= 0:
        merged["iv_source"] = "sse"
        return merged

    merged["iv_source"] = "sse"
    _backfill_otm_iv(merged, spot)
    return merged


def _resolve_underlying_price(chain_df: pd.DataFrame) -> float | None:
    """Infer underlying spot price from chain mid-strike or realtime API.

    Args:
        chain_df: Chain DataFrame with ``underlying`` and ``strike`` columns.

    Returns:
        Underlying price or None if unavailable.
    """
    underlying = "510050"
    if "underlying" in chain_df.columns and not chain_df["underlying"].empty:
        underlying = str(chain_df["underlying"].iloc[0])
    try:
        rt = _api.get_realtime_data(symbol=underlying, source="eastmoney_direct")
        if not rt.empty and "price" in rt.columns:
            price = float(rt["price"].iloc[0])
            if price > 0:
                return price
    except Exception:
        pass
    return None


def _backfill_otm_iv(merged: pd.DataFrame, spot: float) -> None:
    """Back-fill IV for OTM contracts where SSE returned iv=0.

    Uses ``calculate_iv`` (Newton method, BS model) to solve IV from market
    price for OTM contracts that have a valid price but iv=0.

    Args:
        merged: Merged chain+risk DataFrame (modified in-place).
        spot: Underlying spot price.
    """
    from akshare_one.options_indicators import calculate_iv as _calc_iv

    if "option_type" not in merged.columns or "price" not in merged.columns:
        return

    is_otm_call = (merged["option_type"] == "call") & (merged["strike"] >= spot)
    is_otm_put = (merged["option_type"] == "put") & (merged["strike"] <= spot)
    is_otm = is_otm_call | is_otm_put
    needs_backfill = is_otm & (merged["iv"] == 0) & (merged["price"] > 0)

    for idx in merged.index[needs_backfill]:
        row = merged.loc[idx]
        t_years = _expiration_to_years(row.get("expiration", ""))
        if t_years is None or t_years <= 0:
            continue
        try:
            iv = _calc_iv(
                S=spot,
                K=float(row["strike"]),
                T=t_years,
                r=0.02,
                option_price=float(row["price"]),
                option_type=row["option_type"],
                model="bs",
            )
            if 0 < iv < 5:
                merged.at[idx, "iv"] = iv
                merged.at[idx, "iv_source"] = "calculated"
        except Exception:
            continue


def _expiration_to_years(expiration: str) -> float | None:
    """Convert expiration string (e.g. '202603') to years remaining.

    Uses the 3rd Friday of the expiration month as the exercise date, which
    is the standard ETF option expiry rule in China.

    Args:
        expiration: Expiration string in 'YYYYMM' or 'YYYY-MM-DD' format.

    Returns:
        Annualized time to expiry, or None if parsing fails.
    """
    import calendar

    today = _dt.date.today()
    try:
        if len(expiration) == 6:
            year = int(expiration[:4])
            month = int(expiration[4:6])
        elif "-" in expiration:
            exp_date = _dt.datetime.strptime(expiration, "%Y-%m-%d").date()
            delta = (exp_date - today).days
            return delta / 365.0 if delta > 0 else None
        else:
            return None

        cal = calendar.monthcalendar(year, month)
        fridays = [week[calendar.FRIDAY] for week in cal if week[calendar.FRIDAY] != 0]
        exercise_day = fridays[2] if len(fridays) >= 3 else fridays[-1]
        exp_date = _dt.date(year, month, exercise_day)
        delta = (exp_date - today).days
        return delta / 365.0 if delta > 0 else 1.0 / 365.0
    except Exception:
        return None


@mcp.tool
@_tool_error_boundary
def compute_options_iv_smile(
    date: Annotated[str, Field(description="交易日 (YYYYMMDD 或 YYYY-MM-DD)")],
    expiration: Annotated[str, Field(description="到期日过滤（需与 chain expiration 一致）")],
) -> list[dict[str, Any]]:
    """自包含：获取 SSE 风险指标 + 期权链，生成 IV smile（Track B）。

    使用行业标准做法：OTM call (K >= ATM) + OTM put (K <= ATM) 构建微笑曲线。
    深度 ITM 合约被过滤；OTM 区域 iv=0 的合约会尝试用 BS 模型补算。

    Args:
        date: 交易日。
        expiration: 到期日过滤。

    Returns:
        DataFrame records: [expiration, strike, iv, moneyness, iv_source]

    Raises:
        ToolError: 数据获取或计算失败时抛出。
    """
    from akshare_one.options_indicators import get_iv_smile as _iv_smile

    risk_df = _api.get_options_risk_indicators(date=date, source="sse")
    if risk_df.empty:
        return []

    chain_df = _api.get_options_chain(underlying_symbol="510050", source="sina")
    if chain_df.empty:
        return []

    merged = _merge_chain_with_risk(chain_df, risk_df)
    if merged.empty:
        return []

    spot = _resolve_underlying_price(chain_df)
    return _serialize_dataframe(
        _iv_smile(merged, expiration=expiration, underlying_price=spot)
    )


@mcp.tool
@_tool_error_boundary
def compute_options_iv_term_structure(
    date: Annotated[str, Field(description="交易日 (YYYYMMDD 或 YYYY-MM-DD)")],
    underlying_price: Annotated[
        float | None,
        Field(description="标的价格（用于 ATM 识别），不提供则自动获取"),
    ] = None,
) -> list[dict[str, Any]]:
    """自包含：获取 SSE 风险指标 + 期权链，构建 IV 期限结构（Track B）。

    各到期日的 ATM IV 构成期限结构。深度 ITM iv=0 的合约已被过滤。

    Args:
        date: 交易日。
        underlying_price: 标的价格（可选，不提供则自动获取实时价）。

    Returns:
        DataFrame records: [expiration, iv_atm]

    Raises:
        ToolError: 数据获取或计算失败时抛出。
    """
    from akshare_one.options_indicators import get_iv_term_structure as _iv_ts

    risk_df = _api.get_options_risk_indicators(date=date, source="sse")
    if risk_df.empty:
        return []

    chain_df = _api.get_options_chain(underlying_symbol="510050", source="sina")
    if chain_df.empty:
        return []

    merged = _merge_chain_with_risk(chain_df, risk_df)
    if merged.empty:
        return []

    spot = underlying_price or _resolve_underlying_price(chain_df)
    filtered = merged[merged["iv"] > 0].copy()
    if filtered.empty:
        return []

    return _serialize_dataframe(_iv_ts(filtered, underlying_price=spot))


# ---------------------------------------------------------------------------
# Derivatives indicators (Track B) — futures self-contained
# ---------------------------------------------------------------------------

_HV_METHOD = Literal["close_to_close", "parkinson", "garman_klass", "yang_zhang"]


@mcp.tool
@_tool_error_boundary
def compute_futures_hv(
    symbol: Annotated[str, Field(description="期货品种代码 (e.g. 'AG'/'RB')")],
    start_date: Annotated[str, Field(description="开始日期 (YYYY-MM-DD)")],
    end_date: Annotated[str, Field(description="结束日期 (YYYY-MM-DD)")],
    contract: Annotated[str, Field(description="合约代码 ('main' 或如 '2604')")] = "main",
    method: Annotated[
        _HV_METHOD,
        Field(description="HV 方法 (close_to_close/parkinson/garman_klass/yang_zhang)"),
    ] = "close_to_close",
    window: Annotated[int, Field(description="滚动窗口长度")] = 20,
    annualization: Annotated[int, Field(description="年化因子（交易日/年）")] = 252,
) -> list[dict[str, Any]]:
    """自包含：获取期货历史行情并计算历史波动率 HV（Track B）。

    Args:
        symbol: 品种代码。
        start_date: 开始日期。
        end_date: 结束日期。
        contract: 合约代码。
        method: HV 方法。
        window: 窗口长度。
        annualization: 年化因子。

    Returns:
        DataFrame records: [timestamp, hv]

    Raises:
        ToolError: 数据获取或计算失败时抛出。
    """
    from akshare_one.futures_indicators import get_historical_volatility as _hv

    hist_df = _api.get_futures_hist_data(
        symbol=symbol,
        contract=contract,
        interval="day",
        start_date=start_date,
        end_date=end_date,
        source="sina",
    )
    if hist_df.empty:
        return []
    return _serialize_dataframe(
        _hv(ohlc_df=hist_df, method=method, window=window, annualization=annualization)
    )


@mcp.tool
@_tool_error_boundary
def compute_futures_volatility_cone(
    symbol: Annotated[str, Field(description="期货品种代码 (e.g. 'AG'/'RB')")],
    start_date: Annotated[str, Field(description="开始日期 (YYYY-MM-DD)")],
    end_date: Annotated[str, Field(description="结束日期 (YYYY-MM-DD)")],
    contract: Annotated[str, Field(description="合约代码 ('main' 或如 '2604')")] = "main",
    windows: Annotated[
        str,
        Field(description="窗口长度，逗号分隔 (e.g. '5,10,20,60,120')"),
    ] = "5,10,20,60,120",
    percentiles: Annotated[
        str,
        Field(description="分位数(0-1)，逗号分隔 (e.g. '0.1,0.25,0.5,0.75,0.9')"),
    ] = "0.1,0.25,0.5,0.75,0.9",
    method: Annotated[
        _HV_METHOD,
        Field(description="HV 方法 (close_to_close/parkinson/garman_klass/yang_zhang)"),
    ] = "close_to_close",
) -> list[dict[str, Any]]:
    """自包含：获取期货历史行情并计算波动率锥（Track B）。

    Args:
        symbol: 品种代码。
        start_date: 开始日期。
        end_date: 结束日期。
        contract: 合约代码。
        windows: 窗口长度，逗号分隔。
        percentiles: 分位数(0-1)，逗号分隔。
        method: HV 方法。

    Returns:
        DataFrame records: [window, percentile, hv]

    Raises:
        ToolError: 数据获取或计算失败时抛出。
    """
    from akshare_one.futures_indicators import get_volatility_cone as _cone

    parsed_windows = tuple(int(w.strip()) for w in windows.split(",") if w.strip())
    parsed_percentiles = tuple(float(p.strip()) for p in percentiles.split(",") if p.strip())

    hist_df = _api.get_futures_hist_data(
        symbol=symbol,
        contract=contract,
        interval="day",
        start_date=start_date,
        end_date=end_date,
        source="sina",
    )
    if hist_df.empty:
        return []
    return _serialize_dataframe(
        _cone(
            ohlc_df=hist_df,
            windows=parsed_windows,
            percentiles=parsed_percentiles,
            method=method,
            annualization=252,
        )
    )


@mcp.tool
@_tool_error_boundary
def compute_futures_oi_change(
    symbol: Annotated[str, Field(description="期货品种代码 (e.g. 'AG'/'RB')")],
    start_date: Annotated[str, Field(description="开始日期 (YYYY-MM-DD)")],
    end_date: Annotated[str, Field(description="结束日期 (YYYY-MM-DD)")],
    contract: Annotated[str, Field(description="合约代码 ('main' 或如 '2604')")] = "main",
) -> list[dict[str, Any]]:
    """自包含：获取期货历史行情并计算持仓量变化（Track B）。

    Args:
        symbol: 品种代码。
        start_date: 开始日期。
        end_date: 结束日期。
        contract: 合约代码。

    Returns:
        DataFrame records: [timestamp, open_interest, open_interest_change]

    Raises:
        ToolError: 数据获取或计算失败时抛出。
    """
    from akshare_one.futures_indicators import get_futures_oi_change as _oi

    hist_df = _api.get_futures_hist_data(
        symbol=symbol,
        contract=contract,
        interval="day",
        start_date=start_date,
        end_date=end_date,
        source="sina",
    )
    if hist_df.empty:
        return []
    return _serialize_dataframe(_oi(hist_df))


@mcp.tool
@_tool_error_boundary
def compute_futures_price_oi_divergence(
    symbol: Annotated[str, Field(description="期货品种代码 (e.g. 'AG'/'RB')")],
    start_date: Annotated[str, Field(description="开始日期 (YYYY-MM-DD)")],
    end_date: Annotated[str, Field(description="结束日期 (YYYY-MM-DD)")],
    contract: Annotated[str, Field(description="合约代码 ('main' 或如 '2604')")] = "main",
) -> list[dict[str, Any]]:
    """自包含：获取期货历史行情并计算价-仓背离信号（Track B）。

    Args:
        symbol: 品种代码。
        start_date: 开始日期。
        end_date: 结束日期。
        contract: 合约代码。

    Returns:
        DataFrame records: [timestamp, close, open_interest, signal]

    Raises:
        ToolError: 数据获取或计算失败时抛出。
    """
    from akshare_one.futures_indicators import (
        get_futures_price_oi_divergence as _div,
    )

    hist_df = _api.get_futures_hist_data(
        symbol=symbol,
        contract=contract,
        interval="day",
        start_date=start_date,
        end_date=end_date,
        source="sina",
    )
    if hist_df.empty:
        return []
    close_df = hist_df[["timestamp", "close"]]
    oi_df = hist_df[["timestamp", "open_interest"]]
    return _serialize_dataframe(_div(close_df=close_df, oi_df=oi_df))


@mcp.tool
@_tool_error_boundary
def compute_futures_vwap(
    symbol: Annotated[str, Field(description="期货品种代码 (e.g. 'AG'/'RB')")],
    start_date: Annotated[str, Field(description="开始日期 (YYYY-MM-DD)")],
    end_date: Annotated[str, Field(description="结束日期 (YYYY-MM-DD)")],
    contract: Annotated[str, Field(description="合约代码 ('main' 或如 '2604')")] = "main",
) -> list[dict[str, Any]]:
    """自包含：获取期货历史行情并计算 VWAP（Track B）。

    Args:
        symbol: 品种代码。
        start_date: 开始日期。
        end_date: 结束日期。
        contract: 合约代码。

    Returns:
        DataFrame records: [timestamp, vwap]

    Raises:
        ToolError: 数据获取或计算失败时抛出。
    """
    from akshare_one.futures_indicators import get_futures_vwap as _vwap

    hist_df = _api.get_futures_hist_data(
        symbol=symbol,
        contract=contract,
        interval="day",
        start_date=start_date,
        end_date=end_date,
        source="sina",
    )
    if hist_df.empty:
        return []
    return _serialize_dataframe(_vwap(hist_df))


@mcp.tool
@_tool_error_boundary
def compute_futures_term_structure(
    symbol: Annotated[str, Field(description="期货品种代码 (e.g. 'CF'/'RB')")],
) -> list[dict[str, Any]]:
    """自包含：获取期货实时多合约报价并计算期限结构（Track B）。

    Args:
        symbol: 品种代码。

    Returns:
        DataFrame records: [timestamp, curve_slope, curve_level]

    Raises:
        ToolError: 数据获取或计算失败时抛出。
    """
    from akshare_one.futures_indicators import get_futures_term_structure as _ts

    realtime_df = _api.get_futures_realtime_data(symbol=symbol, source="sina")
    if realtime_df.empty:
        return []
    df = realtime_df.copy()
    if "contract" not in df.columns and "symbol" in df.columns:
        df = df.rename(columns={"symbol": "contract"})
    keep = [c for c in ["contract", "price", "timestamp"] if c in df.columns]
    return _serialize_dataframe(_ts(df[keep]))


@mcp.tool
@_tool_error_boundary
def compute_futures_spread(
    symbol: Annotated[str, Field(description="期货品种代码 (e.g. 'AG'/'RB')")],
    near_contract: Annotated[str, Field(description="近月合约代码 (e.g. '2604')")],
    far_contract: Annotated[str, Field(description="远月合约代码 (e.g. '2606')")],
    start_date: Annotated[str, Field(description="开始日期 (YYYY-MM-DD)")],
    end_date: Annotated[str, Field(description="结束日期 (YYYY-MM-DD)")],
) -> list[dict[str, Any]]:
    """自包含：获取两个合约历史行情并计算跨合约价差（Track B）。

    Args:
        symbol: 品种代码。
        near_contract: 近月合约代码。
        far_contract: 远月合约代码。
        start_date: 开始日期。
        end_date: 结束日期。

    Returns:
        DataFrame records: [timestamp, spread]

    Raises:
        ToolError: 数据获取或计算失败时抛出。
    """
    from akshare_one.futures_indicators import get_futures_spread as _spread

    near_df = _api.get_futures_hist_data(
        symbol=symbol,
        contract=near_contract,
        interval="day",
        start_date=start_date,
        end_date=end_date,
        source="sina",
    )
    far_df = _api.get_futures_hist_data(
        symbol=symbol,
        contract=far_contract,
        interval="day",
        start_date=start_date,
        end_date=end_date,
        source="sina",
    )
    if near_df.empty or far_df.empty:
        return []
    return _serialize_dataframe(
        _spread(
            near_df=near_df[["timestamp", "close"]],
            far_df=far_df[["timestamp", "close"]],
        )
    )


@mcp.tool
@_tool_error_boundary
def compute_futures_long_short_ratio(
    date: Annotated[str, Field(description="交易日 (YYYYMMDD 或 YYYY-MM-DD)")],
    symbol: Annotated[str, Field(description="品种代码 (e.g. 'RB', 'AL')")],
    exchange: Annotated[
        Literal["shfe", "dce", "czce", "cffex", "gfex"],
        Field(description="交易所代码"),
    ],
) -> list[dict[str, Any]]:
    """自包含：获取持仓排名并计算多空比（Track B）。

    Args:
        date: 交易日。
        symbol: 品种代码。
        exchange: 交易所代码。

    Returns:
        DataFrame records: [date, exchange, variety, contract, long_short_ratio]

    Raises:
        ToolError: 数据获取或计算失败时抛出。
    """
    from akshare_one.futures_indicators import get_futures_long_short_ratio as _ratio

    rank_df = _api.get_futures_position_ranking(
        date=date,
        symbol=symbol,
        exchange=exchange,
        source="akshare",
    )
    if rank_df.empty:
        return []
    return _serialize_dataframe(_ratio(rank_df))


# ---------------------------------------------------------------------------
# Entrypoint
# ---------------------------------------------------------------------------


def main() -> None:
    """Console script entrypoint — starts MCP server.

    Supports two transport modes via CLI arguments:
        - stdio (default): ``akshare-one-derivative``
        - streamable HTTP: ``akshare-one-derivative --transport http``

    Args:
        None.

    Returns:
        None.

    Raises:
        SystemExit: 当服务器正常退出或遇到致命错误时抛出。
    """
    import argparse

    parser = argparse.ArgumentParser(
        description="akshare-one-derivative MCP server",
    )
    parser.add_argument(
        "--transport",
        choices=["stdio", "http"],
        default="stdio",
        help="Transport mode (default: stdio)",
    )
    parser.add_argument(
        "--host",
        default="127.0.0.1",
        help="Host to bind (HTTP mode only, default: 127.0.0.1)",
    )
    parser.add_argument(
        "--port",
        type=int,
        default=8000,
        help="Port to bind (HTTP mode only, default: 8000)",
    )
    parser.add_argument(
        "--reload",
        action="store_true",
        help="Auto-reload on file changes (HTTP mode only)",
    )
    args = parser.parse_args()

    if args.transport == "http":
        if args.reload:
            import uvicorn

            uvicorn.run(
                "akshare_one.mcp.server:app",
                host=args.host,
                port=args.port,
                reload=True,
            )
        else:
            mcp.run(transport="http", host=args.host, port=args.port)
    else:
        mcp.run()


app = mcp.http_app()
