zs_sec.py

"""
招商证券对接模块
基于 CTP/UFT/OpenCTP 封装专属对接层,简化配置参数
"""

import logging
from typing import Optional, Dict, Any

from ..constant import Exchange, OrderType, Direction, Offset

# 招商证券对接模块日志
logger = logging.getLogger("AIQuant.zs_sec")
logger.setLevel(logging.INFO)


class ZSSecConfig:
    """
    招商证券接口配置类
    """
    def __init__(
        self,
        server_address: str = "",
        port: int = 0,
        account: str = "",
        password: str = "",
        broker_code: str = ""
    ):
        """
        初始化招商证券接口配置

        Args:
            server_address: 服务器地址
            port: 端口号
            account: 账号
            password: 密码
            broker_code: 经纪商代码
        """
        self.server_address = server_address
        self.port = port
        self.account = account
        self.password = password
        self.broker_code = broker_code

    @classmethod
    def from_dict(cls, config: Dict[str, Any]) -> "ZSSecConfig":
        """
        从字典创建配置对象

        Args:
            config: 配置字典

        Returns:
            招商证券接口配置对象
        """
        return cls(
            server_address=config.get("server_address", ""),
            port=config.get("port", 0),
            account=config.get("account", ""),
            password=config.get("password", ""),
            broker_code=config.get("broker_code", "")
        )

    def to_dict(self) -> Dict[str, Any]:
        """
        转换为字典

        Returns:
            配置字典
        """
        return {
            "server_address": self.server_address,
            "port": self.port,
            "account": self.account,
            "password": self.password,
            "broker_code": self.broker_code
        }

    def validate(self) -> bool:
        """
        验证配置是否有效

        Returns:
            配置是否有效的布尔值
        """
        if not self.server_address:
            logger.error("服务器地址不能为空")
            return False
        if self.port <= 0 or self.port > 65535:
            logger.error("端口号必须在 1-65535 之间")
            return False
        if not self.account:
            logger.error("账号不能为空")
            return False
        if not self.password:
            logger.error("密码不能为空")
            return False
        if not self.broker_code:
            logger.error("经纪商代码不能为空")
            return False
        return True


class ZSSecApi:
    """
    招商证券交易接口
    """
    def __init__(self, config: ZSSecConfig):
        """
        初始化招商证券交易接口

        Args:
            config: 招商证券接口配置
        """
        self.config = config
        self.connected = False
        self.login_success = False

    def connect(self) -> bool:
        """
        连接服务器

        Returns:
            连接是否成功的布尔值
        """
        logger.info("正在连接招商证券接口服务器...")
        logger.info(f"服务器地址: {self.config.server_address}:{self.config.port}")
        logger.info(f"账号: {self.config.account}")
        logger.info(f"经纪商代码: {self.config.broker_code}")

        # 这里应该实现实际的连接逻辑
        # 暂时返回模拟成功
        self.connected = True
        logger.info("招商证券接口连接成功")
        return True

    def login(self) -> bool:
        """
        登录账户

        Returns:
            登录是否成功的布尔值
        """
        if not self.connected:
            logger.error("尚未连接服务器,无法登录")
            return False

        logger.info("正在登录招商证券账户...")

        # 这里应该实现实际的登录逻辑
        # 暂时返回模拟成功
        self.login_success = True
        logger.info("招商证券账户登录成功")
        return True

    def disconnect(self) -> bool:
        """
        断开连接

        Returns:
            断开连接是否成功的布尔值
        """
        logger.info("正在断开招商证券接口连接...")
        self.connected = False
        self.login_success = False
        logger.info("招商证券接口连接已断开")
        return True

    def send_order(
        self,
        symbol: str,
        exchange: Exchange,
        price: float,
        volume: int,
        direction: Direction,
        offset: Offset,
        order_type: OrderType = OrderType.LIMIT
    ) -> Optional[str]:
        """
        发送订单

        Args:
            symbol: 股票代码
            exchange: 交易所
            price: 价格
            volume: 数量
            direction: 方向
            offset: 开平类型
            order_type: 订单类型

        Returns:
            订单 ID(如果成功),否则 None
        """
        if not self.connected or not self.login_success:
            logger.error("尚未连接或登录,无法发送订单")
            return None

        logger.info(
            f"发送订单: {symbol} - {direction.value}{offset.value} - "
            f"{price:.2f} * {volume}")

        # 这里应该实现实际的订单发送逻辑
        # 暂时返回模拟订单 ID
        return f"ZS{symbol}{exchange.value}{direction.value}{offset.value}"

    def cancel_order(self, order_id: str) -> bool:
        """
        撤销订单

        Args:
            order_id: 订单 ID

        Returns:
            撤销是否成功的布尔值
        """
        if not self.connected or not self.login_success:
            logger.error("尚未连接或登录,无法撤销订单")
            return False

        logger.info(f"撤销订单: {order_id}")

        # 这里应该实现实际的订单撤销逻辑
        # 暂时返回模拟成功
        return True

    def get_account(self) -> Optional[Dict[str, Any]]:
        """
        获取账户信息

        Returns:
            账户信息字典(如果成功),否则 None
        """
        if not self.connected or not self.login_success:
            logger.error("尚未连接或登录,无法获取账户信息")
            return None

        # 这里应该实现实际的账户信息获取逻辑
        # 暂时返回模拟数据
        return {
            "account": self.config.account,
            "balance": 1000000.00,
            "available": 800000.00,
            "margin": 200000.00,
            "profit": 50000.00
        }

    def get_positions(self) -> Optional[Dict[str, Any]]:
        """
        获取持仓信息

        Returns:
            持仓信息字典(如果成功),否则 None
        """
        if not self.connected or not self.login_success:
            logger.error("尚未连接或登录,无法获取持仓信息")
            return None

        # 这里应该实现实际的持仓信息获取逻辑
        # 暂时返回模拟数据
        return {
            "000001.SZ": {
                "volume": 1000,
                "direction": Direction.LONG,
                "price": 12.50,
                "pnl": 2500.00
            }
        }


# 错误码和常见问题
ZS_ERROR_CODES = {
    1001: "服务器地址错误",
    1002: "端口号错误",
    1003: "账号或密码错误",
    1004: "经纪商代码错误",
    1005: "网络连接失败",
    1006: "登录超时",
    1007: "权限不足",
    1008: "账户被锁定"
}


# 常见问题排查指引
ZS_TROUBLESHOOTING = {
    "连接失败": [
        "检查服务器地址和端口号是否正确",
        "检查网络连接是否正常",
        "确认防火墙是否允许访问该端口",
        "联系招商证券客服确认服务器状态"
    ],
    "登录失败": [
        "检查账号和密码是否正确",
        "确认是否开通了量化交易权限",
        "检查经纪商代码是否正确",
        "确认账户是否被锁定"
    ],
    "订单发送失败": [
        "检查股票代码和交易所是否有效",
        "确认账户是否有足够的资金",
        "检查交易时间是否在正常交易时段",
        "确认是否有持仓限制"
    ]
}


# 默认配置
ZS_DEFAULT_CONFIG = ZSSecConfig(
    server_address="",
    port=0,
    account="",
    password="",
    broker_code=""
)