from copy import copy
from datetime import datetime, timedelta
from threading import Thread, Condition
from typing import Optional
from decimal import Decimal
import shelve
from tzlocal import get_localzone_name
from logging import DEBUG, INFO, WARNING, ERROR
import re

from datetime import datetime
from .ibapi.client import EClient
from .ibapi.common import OrderId, TickAttrib, TickerId
from .ibapi.contract import Contract, ContractDetails
from .ibapi.execution import Execution
from .ibapi.order import Order
from .ibapi.order_state import OrderState
from .ibapi.ticktype import TickType, TickTypeEnum
from .ibapi.wrapper import EWrapper
from .ibapi.common import BarData as IbBarData

from ks_utility import datetimes
from ks_utility.numbers import to_decimal
from ks_trade_api.constant import CHINA_TZ, RetCode, TimeInForce
from ks_trade_api.utility import extract_vt_symbol, get_trading_hours, TradingHours

from ks_trade_api.object import (
    TickData,
    MyOrderData,
    MyTradeData,
    MyPositionData,
    MyAccountData,
    ContractData,
    BarData,
    MyOrderRequest,
    CancelRequest,
    SubscribeRequest,
    HistoryRequest,
    ErrorData
)
from ks_trade_api.constant import (
    Product,
    OrderType,
    Direction,
    Exchange,
    Currency,
    Status,
    OptionType,
    Interval
)
from ks_trade_api.utility import get_file_path, ZoneInfo
from vnpy.trader.event import EVENT_TIMER
from vnpy.event import Event

def raw_symbol_my2ks(raw_symbol: str, exchange: Exchange):
    ret_symbol = raw_symbol
    if exchange == Exchange.SEHK:
        ret_symbol = '0'*(5-len(raw_symbol)) + raw_symbol
    
    return ret_symbol

# ibk港股前面的0不需要
def raw_symbol_ks2my(raw_symbol: str):
    return raw_symbol.lstrip('0')

class IB_TimeInForce():
    DAY = 'DAY'
    IOC = 'IOC'
    GTC = 'GTC'
    GTD = 'GTD'

TIF_KS2MY = {
    TimeInForce.IOC: IB_TimeInForce.IOC,
    TimeInForce.GTC: IB_TimeInForce.GTC,
    TimeInForce.GTD: IB_TimeInForce.DAY
}

class IB_SECTYPE:
    STK = 'STK'
    OPT = 'OPT'
    FUT = 'FUT'
    CASH = 'CASH'
    IND = 'IND'

PRODUCT_KS2MY: dict = {
    Product.EQUITY: IB_SECTYPE.STK,
    Product.OPTION: IB_SECTYPE.OPT,
    Product.FUTURES: IB_SECTYPE.FUT,
    Product.FOREX: IB_SECTYPE.CASH,
    Product.INDEX: IB_SECTYPE.IND
}

# 委托状态映射
STATUS_IB2VT: dict[str, Status] = {
    "ApiPending": Status.SUBMITTING,
    "PendingSubmit": Status.SUBMITTING,
    "PreSubmitted": Status.NOTTRADED,
    "Submitted": Status.NOTTRADED,
    "ApiCancelled": Status.CANCELLED,
    "Cancelled": Status.CANCELLED,
    "Filled": Status.ALLTRADED,
    "Inactive": Status.REJECTED,
}

# 多空方向映射
DIRECTION_VT2IB: dict[Direction, str] = {Direction.LONG: "BUY", Direction.SHORT: "SELL"}
DIRECTION_IB2VT: dict[str, Direction] = {v: k for k, v in DIRECTION_VT2IB.items()}
DIRECTION_IB2VT["BOT"] = Direction.LONG
DIRECTION_IB2VT["SLD"] = Direction.SHORT

# 委托类型映射
ORDERTYPE_VT2IB: dict[OrderType, str] = {
    OrderType.LIMIT: "LMT",
    OrderType.MARKET: "MKT",
    OrderType.STOP: "STP"
}
ORDERTYPE_IB2VT: dict[str, OrderType] = {v: k for k, v in ORDERTYPE_VT2IB.items()}

# 交易所映射
EXCHANGE_VT2IB: dict[Exchange, str] = {
    Exchange.SMART: "SMART",
    Exchange.NYMEX: "NYMEX",
    Exchange.COMEX: "COMEX",
    Exchange.GLOBEX: "GLOBEX",
    Exchange.IDEALPRO: "IDEALPRO",
    Exchange.CME: "CME",
    Exchange.CBOT: "CBOT",
    Exchange.CBOE: "CBOE",
    Exchange.ICE: "ICE",
    Exchange.SEHK: "SEHK",
    Exchange.SSE: "SEHKNTL",
    Exchange.SZSE: "SEHKSZSE",
    Exchange.HKFE: "HKFE",
    Exchange.CFE: "CFE",
    Exchange.TSE: "TSE",
    Exchange.NYSE: "NYSE",
    Exchange.NASDAQ: "NASDAQ",
    Exchange.AMEX: "AMEX",
    Exchange.ARCA: "ARCA",
    Exchange.EDGEA: "EDGEA",
    Exchange.ISLAND: "ISLAND",
    Exchange.BATS: "BATS",
    Exchange.IEX: "IEX",
    Exchange.IBKRATS: "IBKRATS",
    Exchange.OTC: "PINK",
    Exchange.SGX: "SGX",
    Exchange.EUREX: "EUREX",
    Exchange.PSE: 'PSE'
}
EXCHANGE_IB2VT: dict[str, Exchange] = {v: k for k, v in EXCHANGE_VT2IB.items()}


# 产品类型映射
PRODUCT_IB2VT: dict[str, Product] = {
    "STK": Product.EQUITY,
    "CASH": Product.FOREX,
    "CMDTY": Product.SPOT,
    "FUT": Product.FUTURES,
    "OPT": Product.OPTION,
    "FOP": Product.OPTION,
    "CONTFUT": Product.FUTURES,
    "IND": Product.INDEX
}

# 期权类型映射
OPTION_IB2VT: dict[str, OptionType] = {
    "C": OptionType.CALL,
    "CALL": OptionType.CALL,
    "P": OptionType.PUT,
    "PUT": OptionType.PUT
}

# 货币类型映射
CURRENCY_VT2IB: dict[Currency, str] = {
    Currency.USD: "USD",
    Currency.CNY: "CNY",
    Currency.HKD: "HKD",
}

EXCHANGE2SMART: dict = {
    Exchange.NYSE: Exchange.SMART,
    Exchange.NASDAQ: Exchange.SMART,
    Exchange.ARCA: Exchange.SMART,
    Exchange.EDGEA: Exchange.SMART,
    Exchange.ISLAND: Exchange.SMART,
    Exchange.BATS: Exchange.SMART,
    Exchange.IEX: Exchange.SMART,
    Exchange.AMEX: Exchange.SMART,
    Exchange.NYMEX: Exchange.SMART,
    Exchange.COMEX: Exchange.SMART,
    Exchange.CME: Exchange.SMART,
    Exchange.NYMEX: Exchange.SMART
}

EXCHANGE_MY_CURRENCY_MAP: dict = {
    Exchange.SMART: CURRENCY_VT2IB[Currency.USD],
    Exchange.SEHK: CURRENCY_VT2IB[Currency.HKD],
    Exchange.PSE: CURRENCY_VT2IB[Currency.USD]
}

# 切片数据字段映射
TICKFIELD_IB2VT: dict[int, str] = {
    0: "bid_volume_1",
    1: "bid_price_1",
    2: "ask_price_1",
    3: "ask_volume_1",
    4: "last_price",
    5: "last_volume",
    6: "high_price",
    7: "low_price",
    8: "volume",
    9: "pre_close",
    10: "bid",
    11: "ask",
    12: "last",
    13: "model",
    14: "open_price",
    86: "open_interest",
    
    # delay data
    68: "last_price",
    69: "bid_volume_1",
    70: "ask_volume_1",
    71: "last_volume",
    72: "high_price",
    73: "low_price",
    74: "volume",
    75: "pre_close",
    76: "open_price",
    88: "datetime"
}

# 账户类型映射
ACCOUNTFIELD_IB2VT: dict[str, str] = {
    "NetLiquidationByCurrency": "balance",
    "NetLiquidation": "balance",
    "BuyingPower": "buy_power",
    "AvailableFunds": "available_cash"
}

# 数据频率映射
INTERVAL_VT2IB: dict[Interval, str] = {
    Interval.MINUTE: "1 min",
    Interval.HOUR: "1 hour",
    Interval.DAILY: "1 day",
}

JOIN_SYMBOL: str = "-"

class IbApi(EWrapper):
    """IB的API接口"""

    data_filename: str = "ib_contract_data.db"
    data_filepath: str = str(get_file_path(data_filename))

    smart_symbols_data_filename: str = "smart_symbols.db"
    smart_symbols_data_filepath: str = str(get_file_path(smart_symbols_data_filename))

    def __init__(self, parent, port: int = 7497, client_id: int = 666) -> None:
        """构造函数"""
        super().__init__()

        self.parent = parent
        self.gateway_name = parent.gateway_name
        self.status: bool = False

        self.reqid: int = 0
        self.orderid: int = 0
        self.clientid: int = 0
        self.history_reqid: int = 0
        self.account: str = ""

        self.ticks: dict[int, TickData] = {}
        self.orders: dict[str, MyOrderData] = {}
        self.accounts: dict[str, MyAccountData] = {}
        self.contracts: dict[str, ContractData] = {}

        self.subscribed: dict[str, SubscribeRequest] = {}
        self.data_ready: bool = False

        self.history_req: HistoryRequest = None
        self.history_condition: Condition = Condition()
        self.history_buf: list[BarData] = []

        self.reqid_symbol_map: dict[int, str] = {}              # reqid: subscribe tick symbol
        self.reqid_underlying_map: dict[int, Contract] = {}     # reqid: query option underlying

        self.client: EClient = EClient(self)

        self.ib_contracts: dict[str, Contract] = {}

        self.open_orders_fetched = False
        self.completed_orders_fetched = False

        self.connect('127.0.0.1', port, client_id)

        self.smart_symbols = set() # 用于记录智能路由的标的，持仓和订单回执中要改写成smart交易所
        # self.load_smart_symbols_data()

    def connectAck(self) -> None:
        """连接成功回报"""
        self.status = True
        self.log("IB TWS连接成功")

        self.load_contract_data()

        self.data_ready = False

    def connectionClosed(self) -> None:
        """连接断开回报"""
        self.status = False
        self.log("IB TWS连接断开")

    def nextValidId(self, orderId: int) -> None:
        """下一个有效订单号回报"""
        super().nextValidId(orderId)

        if not self.orderid:
            self.orderid = orderId

    def currentTime(self, time: int) -> None:
        """IB当前服务器时间回报"""
        super().currentTime(time)

        dt: datetime = datetime.fromtimestamp(time)
        time_string: str = dt.strftime("%Y-%m-%d %H:%M:%S.%f")

        msg: str = f"服务器时间: {time_string}"
        self.log(msg)

    def error(
        self,
        reqId: TickerId,
        errorCode: int,
        errorString: str,
        advancedOrderRejectJson: str = ""
    ) -> None:
        """具体错误请求回报"""
        super().error(reqId, errorCode, errorString)

        msg: str = f"信息通知，代码：{errorCode}，内容: {errorString}"
        self.log(msg)

        # 2000-2999信息通知不属于报错信息
        if reqId == self.history_reqid and errorCode not in range(2000, 3000):
            self.history_condition.acquire()
            self.history_condition.notify()
            self.history_condition.release()

        #  需要排除错误通知的代码
        # 1. 2000-2999信息通知不属于报错信息
        # 2. 202是撤单代码，不是错误
        exclude_codes = [202]
        if errorCode not in range(2000, 3000) and errorCode not in exclude_codes:
            error: ErrorData = ErrorData(gateway_name=self.gateway_name, msg=msg)
            self.parent.on_error(error)   

        # 行情服务器已连接
        if errorCode == 2104 and not self.data_ready:
            self.data_ready = True

            self.client.reqCurrentTime()

            reqs: list = list(self.subscribed.values())
            self.subscribed.clear()
            # todo!!!
            for req in reqs:
                self.subscribe(req)

    def tickPrice(self, reqId: TickerId, tickType: TickType, price: float, attrib: TickAttrib) -> None:
        """tick价格更新回报"""
        super().tickPrice(reqId, tickType, price, attrib)

        if tickType not in TICKFIELD_IB2VT:
            return

        tick: TickData = self.ticks.get(reqId, None)
        if not tick:
            self.log(f"tickPrice函数收到未订阅的推送，reqId：{reqId}")
            return

        name: str = TICKFIELD_IB2VT[tickType]
        setattr(tick, name, price)

        # 更新tick数据name字段
        contract: ContractData = self.contracts.get(tick.vt_symbol, None)
        if contract:
            tick.name = contract.name

        # 本地计算Forex of IDEALPRO和Spot Commodity的tick时间和最新价格
        if tick.exchange == Exchange.IDEALPRO or "CMDTY" in tick.symbol:
            if not tick.bid_price_1 or not tick.ask_price_1:
                return
            tick.last_price = (tick.bid_price_1 + tick.ask_price_1) / 2
            tick.datetime = datetime.now(CHINA_TZ)

        self.parent.on_tick(copy(tick))

    def tickSize(self, reqId: TickerId, tickType: TickType, size: Decimal) -> None:
        """tick数量更新回报"""
        super().tickSize(reqId, tickType, size)

        if tickType not in TICKFIELD_IB2VT:
            return

        tick: TickData = self.ticks.get(reqId, None)
        if not tick:
            self.log(f"tickSize函数收到未订阅的推送，reqId：{reqId}")
            return

        name: str = TICKFIELD_IB2VT[tickType]
        setattr(tick, name, float(size))

        self.parent.on_tick(copy(tick))

    def tickString(self, reqId: TickerId, tickType: TickType, value: str) -> None:
        """tick字符串更新回报"""
        super().tickString(reqId, tickType, value)

        if tickType != TickTypeEnum.LAST_TIMESTAMP and tickType != TickTypeEnum.DELAYED_LAST_TIMESTAMP:
            return

        tick: TickData = self.ticks.get(reqId, None)
        if not tick:
            self.log(f"tickString函数收到未订阅的推送，reqId：{reqId}")
            return

        dt: datetime = datetime.fromtimestamp(int(value))
        tick.datetime = dt.replace(tzinfo=CHINA_TZ)

        self.parent.on_tick(copy(tick))

    def tickOptionComputation(
        self,
        reqId: TickerId,
        tickType: TickType,
        tickAttrib: int,
        impliedVol: float,
        delta: float,
        optPrice: float,
        pvDividend: float,
        gamma: float,
        vega: float,
        theta: float,
        undPrice: float
    ):
        """tick期权数据推送"""
        super().tickOptionComputation(
            reqId,
            tickType,
            tickAttrib,
            impliedVol,
            delta,
            optPrice,
            pvDividend,
            gamma,
            vega,
            theta,
            undPrice,
        )

        tick: TickData = self.ticks.get(reqId, None)
        if not tick:
            self.log(f"tickOptionComputation函数收到未订阅的推送，reqId：{reqId}")
            return

        prefix: str = TICKFIELD_IB2VT[tickType]

        tick.extra["underlying_price"] = undPrice

        if optPrice:
            tick.extra[f"{prefix}_price"] = optPrice
            tick.extra[f"{prefix}_impv"] = impliedVol
            tick.extra[f"{prefix}_delta"] = delta
            tick.extra[f"{prefix}_gamma"] = gamma
            tick.extra[f"{prefix}_theta"] = theta
            tick.extra[f"{prefix}_vega"] = vega
        else:
            tick.extra[f"{prefix}_price"] = 0
            tick.extra[f"{prefix}_impv"] = 0
            tick.extra[f"{prefix}_delta"] = 0
            tick.extra[f"{prefix}_gamma"] = 0
            tick.extra[f"{prefix}_theta"] = 0
            tick.extra[f"{prefix}_vega"] = 0

    def tickSnapshotEnd(self, reqId: int) -> None:
        """行情切片查询返回完毕"""
        super().tickSnapshotEnd(reqId)

        tick: TickData = self.ticks.get(reqId, None)
        if not tick:
            self.log(f"tickSnapshotEnd函数收到未订阅的推送，reqId：{reqId}")
            return

        self.log(f"{tick.vt_symbol}行情切片查询成功")

    def orderStatus(
        self,
        orderId: OrderId,
        status: str,
        filled: Decimal,
        remaining: Decimal,
        avgFillPrice: float,
        permId: int,
        parentId: int,
        lastFillPrice: float,
        clientId: int,
        whyHeld: str,
        mktCapPrice: float,
    ) -> None:
        """订单状态更新回报"""
        super().orderStatus(
            orderId,
            status,
            filled,
            remaining,
            avgFillPrice,
            permId,
            parentId,
            lastFillPrice,
            clientId,
            whyHeld,
            mktCapPrice,
        )

        orderid: str = str(orderId)
        order: MyOrderData = self.orders.get(orderid, None)
        if not order:
            return

        order.traded = Decimal(str(filled))

        # 过滤撤单中状态
        order_status: Status = STATUS_IB2VT.get(status, None)
        if order_status:
            order.status = order_status

        self.on_order(copy(order))

    def openOrder(
        self,
        orderId: OrderId,
        ib_contract: Contract,
        ib_order: Order,
        orderState: OrderState,
    ) -> None:
        """新订单回报"""
        super().openOrder(orderId, ib_contract, ib_order, orderState)

        orderid: str = str(orderId)

        dt: datetime = datetimes.now()

        # 优先使用本地缓存的委托记录，解决交易所传SMART时，返回数据的交易所可能发生变化的问题
        order: MyOrderData = self.orders.get(orderid, None)
        if not order:
            exchange = EXCHANGE_IB2VT.get(ib_contract.exchange, Exchange.SMART)
            exchange = EXCHANGE2SMART.get(exchange, exchange) 
            order: MyOrderData = MyOrderData(
                symbol=self.generate_symbol(ib_contract),
                exchange=exchange,
                type=ORDERTYPE_IB2VT[ib_order.orderType],
                orderid=orderid,
                direction=DIRECTION_IB2VT[ib_order.action],
                volume=Decimal(str(ib_order.totalQuantity)),
                datetime=dt,
                gateway_name=self.gateway_name,
            )

        if order.type == OrderType.LIMIT:
            order.price = Decimal(str(ib_order.lmtPrice))
        elif order.type == OrderType.STOP:
            order.price = Decimal(str(ib_order.auxPrice))

        self.orders[orderid] = order
        self.on_order(copy(order))

    def completedOrder(self, ib_contract: Contract, ib_order: Order,
                  orderState: OrderState):
        super().completedOrder(ib_contract, ib_order, orderState)

        orderid: str = str(ib_order.orderId)

        dt: datetime = datetimes.now()

        # 优先使用本地缓存的委托记录，解决交易所传SMART时，返回数据的交易所可能发生变化的问题
        order: MyOrderData = self.orders.get(orderid, None)
        if not order:
            exchange = EXCHANGE_IB2VT.get(ib_contract.exchange, Exchange.SMART)
            exchange = EXCHANGE2SMART.get(exchange, exchange) 
            order: MyOrderData = MyOrderData(
                symbol=self.generate_symbol(ib_contract),
                exchange=exchange,
                type=ORDERTYPE_IB2VT[ib_order.orderType],
                orderid=orderid,
                status=STATUS_IB2VT[orderState.status],
                direction=DIRECTION_IB2VT[ib_order.action],
                volume=Decimal(str(ib_order.totalQuantity)),
                traded=Decimal(str(ib_order.filledQuantity)),
                datetime=dt,
                gateway_name=self.gateway_name,
            )

        if order.type == OrderType.LIMIT:
            order.price = Decimal(str(ib_order.lmtPrice))
        elif order.type == OrderType.STOP:
            order.price = Decimal(str(ib_order.auxPrice))

        self.orders[orderid] = order
        self.on_order(copy(order))

    def on_order(self, order: MyOrderData) -> None:
        # self.check_smart_exchange(order)
        parent = self.parent
        parent.on_order(order)
        if parent.setting.get('log.order', True):
            parent.log(order, tag='订单回调', level=DEBUG) # todo! 是否影藏吊

    def on_position(self, position: MyPositionData) -> None:
        # self.check_smart_exchange(position)
        self.parent.on_position(position)

    def on_account(self, account: MyAccountData) -> None:
        self.parent.on_account(account)

    # def check_smart_exchange(self, obj: object) -> None:
    #     obj.exchange = EXCHANGE2SMART.get(obj.exchange, obj.exchange) 

    def openOrderEnd(self):
        super().openOrderEnd()
        self.open_orders_fetched = True
        if self.open_orders_fetched and self.completed_orders_fetched:
            self.parent.on_orders_end()

    def completedOrdersEnd(self):
        super().completedOrdersEnd()
        self.completed_orders_fetched = True
        if self.open_orders_fetched and self.completed_orders_fetched:
            self.parent.on_orders_end()

    def positionEnd(self):
        super().positionEnd()
        self.parent.on_positions_end_()

    def accountDownloadEnd(self, _account: str):
        # sent after updateAccountValue and updatePortfolio both finished
        super().accountDownloadEnd(_account)
        self.parent.on_accounts_end()

    def reqAccountUpdates(self) -> None:
        if not self.account:
            return
        self.client.reqAccountUpdates(True, self.account)

    def updateAccountValue(self, key: str, val: str, currency: str, accountName: str) -> None:
        """账号更新回报"""
        super().updateAccountValue(key, val, currency, accountName)

        if not currency or key not in ACCOUNTFIELD_IB2VT:
            return

        accountid: str = f"{accountName}.{currency}"
        account: MyAccountData = self.accounts.get(accountid, None)
        if not account:
            try:
                cur = Currency(currency)
            except:
                return
            account = MyAccountData(
                accountid=accountid,
                gateway_name=self.gateway_name,
                currency=Currency(currency)
            )
            self.accounts[accountid] = account

        name: str = ACCOUNTFIELD_IB2VT[key]
        setattr(account, name, to_decimal(val))

    def position(self, account: str, contract: Contract, position: Decimal,
                 avgCost: float):
        super().position(account, contract, position, avgCost)

        exchange: Exchange = EXCHANGE_IB2VT.get(contract.exchange or contract.primaryExchange, Exchange.SMART)
        exchange = EXCHANGE2SMART.get(exchange, exchange)

        if not exchange:
            msg: str = f"存在不支持的交易所持仓：{self.generate_symbol(contract)} {contract.exchange} {contract.primaryExchange}"
            self.log(msg)
            return

        pos: MyPositionData = MyPositionData(
            symbol=self.generate_symbol(contract),
            exchange=exchange,
            direction=Direction.NET,
            volume=Decimal(str(position)),
            price=Decimal(str(avgCost)),
            gateway_name=self.gateway_name,
        )
        self.parent.on_position_(pos)

    def updatePortfolio(
        self,
        contract: Contract,
        position: Decimal,
        marketPrice: float,
        marketValue: float,
        averageCost: float,
        unrealizedPNL: float,
        realizedPNL: float,
        accountName: str,
    ) -> None:
        """持仓更新回报"""
        super().updatePortfolio(
            contract,
            position,
            marketPrice,
            marketValue,
            averageCost,
            unrealizedPNL,
            realizedPNL,
            accountName,
        )

        exchange: Exchange = EXCHANGE_IB2VT.get(contract.exchange or contract.primaryExchange, Exchange.SMART)
        exchange = EXCHANGE2SMART.get(exchange, exchange)

        if not exchange:
            msg: str = f"存在不支持的交易所持仓：{self.generate_symbol(contract)} {contract.exchange} {contract.primaryExchange}"
            self.log(msg)
            return

        try:
            ib_size: int = int(contract.multiplier)
        except ValueError:
            ib_size = 1
        price = averageCost / ib_size

        pos: MyPositionData = MyPositionData(
            symbol=self.generate_symbol(contract),
            exchange=exchange,
            direction=Direction.NET,
            volume=Decimal(str(position)),
            price=Decimal(str(price)),
            pnl=unrealizedPNL,
            gateway_name=self.gateway_name,
        )
        self.on_position(pos)

    def updateAccountTime(self, timeStamp: str) -> None:
        """账号更新时间回报"""
        super().updateAccountTime(timeStamp)
        for account in self.accounts.values():
            self.on_account(copy(account))

    def contractDetails(self, reqId: int, contractDetails: ContractDetails) -> None:
        """合约数据更新回报"""
        super().contractDetails(reqId, contractDetails)

        # 提取合约信息
        ib_contract: Contract = contractDetails.contract

        # 处理合约乘数为0的情况
        if not ib_contract.multiplier:
            ib_contract.multiplier = 1

        # 字符串风格的代码，需要从缓存中获取
        if reqId in self.reqid_symbol_map:
            symbol: str = self.reqid_symbol_map[reqId]
        # 否则默认使用数字风格代码
        else:
            symbol: str = str(ib_contract.conId)

        # 过滤不支持的类型
        product: Product = PRODUCT_IB2VT.get(ib_contract.secType, None)
        if not product:
            return

        # 生成合约
        exchange: Exchange = EXCHANGE_IB2VT[ib_contract.exchange]
        exchange = EXCHANGE2SMART.get(exchange, exchange)
        contract: ContractData = ContractData(
            symbol=symbol,
            exchange=exchange,
            name=contractDetails.longName,
            product=PRODUCT_IB2VT[ib_contract.secType],
            size=float(ib_contract.multiplier),
            pricetick=contractDetails.minTick,
            min_volume=contractDetails.minSize,
            net_position=True,
            history_data=True,
            stop_supported=True,
            gateway_name=self.gateway_name,
        )

        if contract.product == Product.OPTION:
            underlying_symbol: str = str(contractDetails.underConId)

            contract.option_portfolio = underlying_symbol + "_O"
            contract.option_type = OPTION_IB2VT.get(ib_contract.right, None)
            contract.option_strike = ib_contract.strike
            contract.option_index = str(ib_contract.strike)
            contract.option_expiry = datetime.strptime(ib_contract.lastTradeDateOrContractMonth, "%Y%m%d")
            contract.option_underlying = underlying_symbol + "_" + ib_contract.lastTradeDateOrContractMonth

        if contract.vt_symbol not in self.contracts:
            self.parent.on_contract(contract)

            self.contracts[contract.vt_symbol] = contract
            self.ib_contracts[contract.vt_symbol] = ib_contract

    def contractDetailsEnd(self, reqId: int) -> None:
        """合约数据更新结束回报"""
        super().contractDetailsEnd(reqId)

        # 只需要对期权查询做处理
        underlying: Contract = self.reqid_underlying_map.get(reqId, None)
        if not underlying:
            return

        # 输出日志信息
        symbol: str = self.generate_symbol(underlying)
        exchange: Exchange = EXCHANGE_IB2VT.get(underlying.exchange, Exchange.SMART)
        exchange = EXCHANGE2SMART.get(exchange, exchange)
        vt_symbol: str = f"{symbol}.{exchange.value}"

        self.log(f"{vt_symbol}期权链查询成功")

        # 保存期权合约到文件
        self.save_contract_data()

    def execDetails(self, reqId: int, contract: Contract, execution: Execution) -> None:
        """交易数据更新回报"""
        super().execDetails(reqId, contract, execution)

        time_str: str = execution.time
        time_split: list = time_str.split(" ")
        words_count: int = 3

        if len(time_split) == words_count:
            timezone = time_split[-1]
            time_str = time_str.replace(f" {timezone}", "")
            tz = ZoneInfo(timezone)
        elif len(time_split) == (words_count - 1):
            tz = CHINA_TZ
        else:
            self.log(f"收到不支持的时间格式：{time_str}")
            return

        dt: datetime = datetime.strptime(time_str, "%Y%m%d %H:%M:%S")
        dt: datetime = dt.replace(tzinfo=tz)

        if tz != CHINA_TZ:
            dt = dt.astimezone(CHINA_TZ)

        exchange: Exchange = EXCHANGE_IB2VT.get(contract.exchange, Exchange.SMART)
        exchange = EXCHANGE2SMART.get(exchange, exchange)
        trade: MyTradeData = MyTradeData(
            symbol=self.generate_symbol(contract),
            exchange=exchange,
            orderid=str(execution.orderId),
            tradeid=str(execution.execId),
            direction=DIRECTION_IB2VT[execution.side],
            price=Decimal(str(execution.price)),
            volume=Decimal(str(execution.shares)),
            datetime=dt,
            gateway_name=self.gateway_name,
        )

        parent = self.parent
        parent.on_trade(trade)
        if parent.setting.get('log.trade', False):
            parent.log(trade, tag="成交回调", level=DEBUG)

    def managedAccounts(self, accountsList: str) -> None:
        """所有子账户回报"""
        super().managedAccounts(accountsList)

        if not self.account:
            for account_code in accountsList.split(","):
                if account_code:
                    self.account = account_code

        self.log(f"当前使用的交易账号为{self.account}")
        self.client.reqAccountUpdates(True, self.account)

    def historicalData(self, reqId: int, ib_bar: IbBarData) -> None:
        """历史数据更新回报"""
        # 日级别数据和周级别日期数据的数据形式为%Y%m%d
        time_str: str = ib_bar.date
        time_split: list = time_str.split(" ")
        words_count: int = 3

        if ":" not in time_str:
            words_count -= 1

        if len(time_split) == words_count:
            timezone = time_split[-1]
            time_str = time_str.replace(f" {timezone}", "")
            tz = ZoneInfo(timezone)
        elif len(time_split) == (words_count - 1):
            tz = CHINA_TZ
        else:
            self.log(f"收到不支持的时间格式：{time_str}")
            return

        if ":" in time_str:
            dt: datetime = datetime.strptime(time_str, "%Y%m%d %H:%M:%S")
        else:
            dt: datetime = datetime.strptime(time_str, "%Y%m%d")
        dt: datetime = dt.replace(tzinfo=tz)

        if tz != CHINA_TZ:
            dt: datetime = dt.astimezone(CHINA_TZ)

        bar: BarData = BarData(
            symbol=self.history_req.symbol,
            exchange=self.history_req.exchange,
            datetime=dt,
            interval=self.history_req.interval,
            volume=float(ib_bar.volume),
            open_price=ib_bar.open,
            high_price=ib_bar.high,
            low_price=ib_bar.low,
            close_price=ib_bar.close,
            gateway_name=self.gateway_name
        )
        if bar.volume < 0:
            bar.volume = 0

        self.history_buf.append(bar)

    def historicalDataEnd(self, reqId: int, start: str, end: str) -> None:
        """历史数据查询完毕回报"""
        self.history_condition.acquire()
        self.history_condition.notify()
        self.history_condition.release()

    def connect(
        self,
        host: str,
        port: int,
        clientid: int,
        account: str = None,
    ) -> None:
        """连接TWS"""
        if self.status:
            return

        self.host = host
        self.port = port
        self.clientid = clientid
        self.account = account

        self.client.connect(host, port, clientid)
        self.thread = Thread(target=self.client.run)
        self.thread.start()

    def check_connection(self) -> None:
        """检查连接"""
        if self.client.isConnected():
            return

        if self.status:
            self.close()

        self.client.connect(self.host, self.port, self.clientid)

        self.thread = Thread(target=self.client.run)
        self.thread.start()

    def close(self) -> None:
        """断开TWS连接"""
        if not self.status:
            return

        self.save_contract_data()

        self.status = False
        self.client.disconnect()

    def query_option_portfolio(self, underlying: Contract) -> None:
        """查询期权链合约数据"""
        if not self.status:
            return

        # 解析IB期权合约
        ib_contract: Contract = Contract()
        ib_contract.symbol = underlying.symbol
        ib_contract.exchange = underlying.exchange
        ib_contract.currency = underlying.currency

        if underlying.secType == "FUT":
            ib_contract.secType = "FOP"
        else:
            ib_contract.secType = "OPT"

        # 通过TWS查询合约信息
        self.reqid += 1
        self.client.reqContractDetails(self.reqid, ib_contract)

        # 缓存查询记录
        self.reqid_underlying_map[self.reqid] = underlying

    def subscribe(self, req: SubscribeRequest) -> None:
        """订阅tick数据更新"""
        if not self.status:
            return

        if req.exchange not in EXCHANGE_VT2IB:
            self.log(f"不支持的交易所{req.exchange}")
            return

        if " " in req.symbol:
            self.log("订阅失败，合约代码中包含空格")
            return

        # 过滤重复订阅
        if req.vt_symbol in self.subscribed:
            return
        self.subscribed[req.vt_symbol] = req

        # 解析IB合约详情
        ib_contract: Contract = self.generate_ib_contract(req)
        if not ib_contract:
            self.log("代码解析失败，请检查格式是否正确")
            return

        # 通过TWS查询合约信息
        self.reqid += 1
        self.client.reqContractDetails(self.reqid, ib_contract)

        # 如果使用了字符串风格的代码，则需要缓存
        if "-" in req.symbol:
            self.reqid_symbol_map[self.reqid] = req.symbol

        #  订阅tick数据并创建tick对象缓冲区
        self.reqid += 1
        self.client.reqMarketDataType(3) # todo!!!
        self.client.reqMktData(self.reqid, ib_contract, "", False, False, [])

        tick: TickData = TickData(
            symbol=req.symbol,
            exchange=req.exchange,
            datetime=datetime.now(CHINA_TZ),
            gateway_name=self.gateway_name
        )
        tick.extra = {}

        self.ticks[self.reqid] = tick

    def send_order(self, req: MyOrderRequest) -> str:
        """委托下单"""
        if not self.status:
            return ""

        if req.exchange not in EXCHANGE_VT2IB:
            self.log(f"不支持的交易所：{req.exchange}")
            return ""

        if req.type not in ORDERTYPE_VT2IB:
            self.log(f"不支持的价格类型：{req.type}")
            return ""

        if " " in req.symbol:
            self.log("委托失败，合约代码中包含空格")
            return ""

        self.orderid += 1

        ib_contract: Contract = self.generate_ib_contract(req)
        if not ib_contract:
            return ""

        ib_order: Order = Order()
        ib_order.orderId = self.orderid
        ib_order.clientId = self.clientid
        ib_order.action = DIRECTION_VT2IB[req.direction]
        ib_order.orderType = ORDERTYPE_VT2IB[req.type]
        ib_order.totalQuantity = Decimal(req.volume)
        ib_order.account = self.account
        ib_order.orderRef = req.reference
        ib_order.tif = TIF_KS2MY.get(req.time_in_force)
        ib_order.outsideRth = True # 允许盘前盘后

         # 如果是美股切是夜盘时间，需要把exchange设置为OVERNIGHT
        trading_hours: TradingHours = get_trading_hours()
        if req.exchange == Exchange.SMART and trading_hours == TradingHours.OVER_NIGHT:
            # ibkr客服: 但注意如果你把訂單傳到OVERNIGHT，你不須定義`order.outsideRth = True`。
            ib_contract.exchange = 'OVERNIGHT'
        else:
            ib_order.outsideRth = True 

        if req.type == OrderType.LIMIT:
            ib_order.lmtPrice = req.price
        elif req.type == OrderType.STOP:
            ib_order.auxPrice = req.price

        self.client.placeOrder(self.orderid, ib_contract, ib_order)
        self.client.reqIds(1)

        order: MyOrderData = req.create_order_data(str(self.orderid), self.gateway_name)
        self.orders[order.orderid] = order
        self.on_order(order)
        return order.vt_orderid

    def cancel_order(self, orderid: int) -> None:
        """委托撤单"""
        self.client.cancelOrder(int(orderid), "")

    def query_contract(self, vt_symbol: str) -> None:
        symbol, exchange = extract_vt_symbol(vt_symbol)
        exchange = EXCHANGE_VT2IB.get(exchange)
        exchange = EXCHANGE2SMART.get(exchange, exchange)
        ib_contract: Contract = Contract()
        ib_contract.secType = IB_SECTYPE.STK
        ib_contract.symbol = raw_symbol_ks2my(symbol)
        ib_contract.exchange = exchange
        ib_contract.currency = EXCHANGE_MY_CURRENCY_MAP[exchange]

        # 通过TWS查询合约信息
        self.reqid += 1
        self.client.reqContractDetails(self.reqid, ib_contract)

    def query_history(self, req: HistoryRequest) -> list[BarData]:
        """查询历史数据"""
        contract: ContractData = self.contracts[req.vt_symbol]
        if not contract:
            self.log(f"找不到合约：{req.vt_symbol}，请先订阅")
            return []

        self.history_req = req

        self.reqid += 1

        ib_contract: Contract = self.generate_ib_contract(req)

        if req.end:
            end: datetime = req.end
        else:
            end: datetime = datetime.now(CHINA_TZ)

        # 使用UTC结束时间戳
        utc_tz: ZoneInfo = ZoneInfo("utc")
        utc_end: datetime = end.astimezone(utc_tz)
        end_str: str = utc_end.strftime("%Y%m%d-%H:%M:%S")

        delta: timedelta = end - req.start
        days: int = delta.days
        if days < 365:
            duration: str = f"{days} D"
        else:
            duration: str = f"{delta.days/365:.0f} Y"

        bar_size: str = INTERVAL_VT2IB[req.interval]

        if contract.product in [Product.SPOT, Product.FOREX]:
            bar_type: str = "MIDPOINT"
        else:
            bar_type: str = "TRADES"

        self.history_reqid = self.reqid
        self.client.reqHistoricalData(
            self.reqid,
            ib_contract,
            end_str,
            duration,
            bar_size,
            bar_type,
            0,
            1,
            False,
            []
        )

        self.history_condition.acquire()    # 等待异步数据返回
        self.history_condition.wait(600)
        self.history_condition.release()

        history: list[BarData] = self.history_buf
        self.history_buf: list[BarData] = []       # 创新新的缓冲列表
        self.history_req: HistoryRequest = None

        return history

    def load_contract_data(self) -> None:
        """加载本地合约数据"""
        f = shelve.open(self.data_filepath)
        self.contracts = f.get("contracts", {})
        self.ib_contracts = f.get("ib_contracts", {})
        f.close()

        for contract in self.contracts.values():
            self.parent.on_contract(contract)

        self.log("本地缓存合约信息加载成功")

    def save_contract_data(self) -> None:
        """保存合约数据至本地"""
        # 保存前确保所有合约数据接口名称为IB，避免其他模块的处理影响
        contracts: dict[str, ContractData] = {}
        for vt_symbol, contract in self.contracts.items():
            c: ContractData = copy(contract)
            c.gateway_name = "IB"
            contracts[vt_symbol] = c

        f = shelve.open(self.data_filepath)
        f["contracts"] = contracts
        f["ib_contracts"] = self.ib_contracts
        f.close()

    def update_smart_symbols(self, symbols: list[str]) -> None:
        self.smart_symbols.update(symbols)
        self.save_smart_symbols_data()

    def load_smart_symbols_data(self) -> None:
        """加载智能合约代码"""
        f = shelve.open(self.smart_symbols_data_filepath)
        self.smart_symbols = f.get("smart_symbols", set())
        f.close()

        self.log("本地缓存合约信息加载成功")

    def save_smart_symbols_data(self) -> None:
        """保存合约数据至本地"""
        f = shelve.open(self.smart_symbols_data_filepath)
        f["smart_symbols"] = self.smart_symbols
        f.close()

    def generate_symbol(self, ib_contract: Contract) -> str:
        """生成合约代码"""
        # 生成字符串风格代码
        # fields: list = [ib_contract.symbol]

        # if ib_contract.secType in ["FUT", "OPT", "FOP"]:
        #     fields.append(ib_contract.lastTradeDateOrContractMonth)

        # if ib_contract.secType in ["OPT", "FOP"]:
        #     fields.append(ib_contract.right)
        #     fields.append(str(ib_contract.strike))
        #     fields.append(str(ib_contract.multiplier))

        # fields.append(ib_contract.currency)
        # fields.append(ib_contract.secType)

        # symbol: str = JOIN_SYMBOL.join(fields)
        exchange: Exchange = EXCHANGE_IB2VT.get(ib_contract.primaryExchange or ib_contract.exchange)
        symbol: str = raw_symbol_my2ks(ib_contract.symbol, exchange)
        if ib_contract.secType == IB_SECTYPE.OPT:
            # 形式：'COIN230815C82000'
            symbol = f'{ib_contract.symbol}{ib_contract.lastTradeDateOrContractMonth[2:]}{ib_contract.right}{int(ib_contract.strike*1000)}'
        # vt_symbol: str = f"{symbol}.{exchange.value}"

        # 在合约信息中找不到字符串风格代码，则使用数字代码
        # if vt_symbol not in self.contracts:
        #     symbol, exchange = extract_vt_symbol(vt_symbol)
        #     ib_contract = generate_ib_contract(symbol, exchange)
        #     self.reqid += 1
        #     self.client.reqContractDetails(self.reqid, ib_contract)
        #     symbol = str(ib_contract.conId)

        return symbol

    def query_tick(self, vt_symbol: str) -> None:
        """查询行情切片"""
        if not self.status:
            return

        contract: ContractData = self.contracts.get(vt_symbol, None)
        if not contract:
            self.log(f"查询行情切片失败，找不到{vt_symbol}对应的合约数据")
            return

        ib_contract: Contract = self.ib_contracts.get(vt_symbol, None)
        if not contract:
            self.log(f"查询行情切片失败，找不到{vt_symbol}对应的IB合约数据")
            return

        self.reqid += 1
        self.client.reqMktData(self.reqid, ib_contract, "", True, False, [])

        tick: TickData = TickData(
            symbol=contract.symbol,
            exchange=contract.exchange,
            datetime=datetime.now(CHINA_TZ),
            gateway_name=self.gateway_name
        )
        tick.extra = {}

        self.ticks[self.reqid] = tick

    def unsubscribe(self, req: SubscribeRequest) -> None:
        """退订tick数据更新"""
        # 移除订阅记录
        if req.vt_symbol not in self.subscribed:
            return
        self.subscribed.pop(req.vt_symbol)

        # 获取订阅号
        cancel_id: int = 0
        for reqid, tick in self.ticks.items():
            if tick.vt_symbol == req.vt_symbol:
                cancel_id = reqid
                break

        # 发送退订请求
        self.client.cancelMktData(cancel_id)

    def generate_ib_contract(self, req: MyOrderRequest) -> Optional[Contract]:
        """生产IB合约"""
        vt_symbol: str = req.vt_symbol
        symbol, exchange = extract_vt_symbol(vt_symbol)
        ib_contract: Contract = Contract()
        ib_contract.secType = PRODUCT_KS2MY.get(req.product ,IB_SECTYPE.STK)
        ib_contract.symbol = raw_symbol_ks2my(symbol)
        ib_contract.exchange = EXCHANGE_VT2IB.get(exchange)
        ib_contract.currency = EXCHANGE_MY_CURRENCY_MAP[exchange]

        if req.product == Product.OPTION:
            ib_contract.secType = "OPT"
            # 形式：'COIN230815C82000'
            result = re.match("(\w+)(\d{6})([C|P])(\d+)", symbol)
            if result: 
                symbol, expiry, right, strike = result.groups()
                ib_contract.symbol = symbol
                ib_contract.lastTradeDateOrContractMonth = '20' + expiry
                ib_contract.right = right
                ib_contract.strike = int(strike)/1000
            else:
                self.log(symbol, tag='解析期权代码错误', level=ERROR)
        return ib_contract

    def log(self, msg='hello_log', tag=[], level=INFO, title=None, name=None, fn_level=1):
        return self.parent.log(msg=msg, tag=tag, level=level, title=title, name=name, fn_level=2)
