"""Quote SPI (callback) implementation for the quote demo.

Modeled after `demo/xlt_quote_api_demo/XLTQuoteDemo`.
Callback output format matches C++ demo printf exactly.

Important:
- Callback names must match the SWIG director methods.
- Never raise exceptions from callbacks; SWIG will terminate the process.
"""

from __future__ import annotations

import logging
import traceback
from typing import TYPE_CHECKING

import py_xlt_api as xlt

if TYPE_CHECKING:
    try:
        from quote_demo import XLTQuoteDemo
    except ImportError:  # pragma: no cover
        from .quote_demo import XLTQuoteDemo

_log = logging.getLogger(__name__)


def _cb_guard(name: str):
    """Decorator: never let exceptions escape SWIG director callbacks.

    注意: 回调函数必须快速返回，若需要异步处理数据，请先拷贝一份数据。
    回调结束之后，参数指向的内存会失效。
    NOTE: Callbacks must return quickly. If you need to process data
    asynchronously, copy the data first. Memory becomes invalid after return.
    """
    def deco(fn):
        def wrapped(self, *args, **kwargs):
            if self._shutting_down:
                return None
            try:
                return fn(self, *args, **kwargs)
            except BaseException:
                _log.error("Uncaught exception in callback %s", name)
                _log.error(traceback.format_exc())
                return None

        return wrapped

    return deco


class _QuoteSpi(xlt.XLTQuoteSpi):
    """Quote SPI implementation.

    注意: 所有回调函数必须快速返回！参数指向的内存在回调返回后即失效。
    若需要异步处理数据，请在回调中先拷贝一份数据再返回。
    """
    def __init__(self, owner: "XLTQuoteDemo"):
        super().__init__()
        self._owner = owner
        self._shutting_down = False

    @_cb_guard("on_disconnect")
    def on_disconnect(self, session_id: int):
        print("on_disconnect: { session_id = %d }" % int(session_id))
        self._owner._session_id = 0
        self._owner._login_success = False
        self._owner._login_finished = False
        self._owner._login_event.set()

    @_cb_guard("on_login")
    def on_login(self, session_id: int, request_id: int, error_info):
        if error_info and error_info.error_code_type != 0:
            print("on_login failed: { error_code_type = %d, error_code = %d, error_msg = %s }"
                  % (int(error_info.error_code_type), int(error_info.error_code), error_info.error_msg))
            self._owner._session_id = 0
            self._owner._login_success = False
        else:
            print("on_login success: { session_id = %d, request_id = %d }"
                  % (int(session_id), int(request_id)))
            self._owner._session_id = int(session_id)
            self._owner._login_success = True

        self._owner._login_finished = True
        self._owner._login_event.set()

    @_cb_guard("on_query_static_quote_info")
    def on_query_static_quote_info(self, session_id, request_id, static_quotes, is_last, error_info):
        data_count = static_quotes.data_count() if static_quotes else 0
        print("on_query_static_quote_info: { session_id = %d, request_id = %d, data_count = %d, is_last = %d }"
              % (int(session_id), int(request_id), int(data_count), int(is_last)))

        if error_info and error_info.error_code_type != 0:
            print("  error: { error_code_type = %d, error_code = %d, error_msg = %s }"
                  % (int(error_info.error_code_type), int(error_info.error_code), error_info.error_msg))
            return

        for i in range(int(data_count)):
            info:xlt.xlt_static_quote_full_info_t = static_quotes.get(i)
            print("  [%d]: { list_date = %d, exchange_index = %d, security_code = %s, security_name = %s, security_type = %d, "
                  "is_registration = %d, is_vie = %d, is_noprofit = %d, is_weighted_voting_rights = %d, "
                  "is_have_price_limit = %d, upper_limit_price = %d, lower_limit_price = %d, "
                  "pre_close_price = %d, price_tick = %d, "
                  "bid_qty_upper_limit = %d, bid_qty_lower_limit = %d, bid_qty_unit = %d, "
                  "ask_qty_upper_limit = %d, ask_qty_lower_limit = %d, ask_qty_unit = %d, "
                  "market_bid_qty_upper_limit = %d, market_bid_qty_lower_limit = %d, market_bid_qty_unit = %d, "
                  "market_ask_qty_upper_limit = %d, market_ask_qty_lower_limit = %d, market_ask_qty_unit = %d, "
                  "security_status = %s, "
                  "underlying_code = %s, swap_flag = %d, swap_price = %d, maturity_date = %d }"
                  % (i,
                     int(info.list_date),
                     int(info.exchange_index), info.security_code, info.security_name, int(info.security_type),
                     int(info.is_registration), int(info.is_vie), int(info.is_noprofit), int(info.is_weighted_voting_rights),
                     int(info.is_have_price_limit), int(info.upper_limit_price), int(info.lower_limit_price),
                     int(info.pre_close_price), int(info.price_tick),
                     int(info.bid_qty_upper_limit), int(info.bid_qty_lower_limit), int(info.bid_qty_unit),
                     int(info.ask_qty_upper_limit), int(info.ask_qty_lower_limit), int(info.ask_qty_unit),
                     int(info.market_bid_qty_upper_limit), int(info.market_bid_qty_lower_limit), int(info.market_bid_qty_unit),
                     int(info.market_ask_qty_upper_limit), int(info.market_ask_qty_lower_limit), int(info.market_ask_qty_unit),
                     info.security_status,
                     info.underlying_code, int(info.swap_flag), int(info.swap_price), int(info.maturity_date)))

    @_cb_guard("on_query_level1_quote")
    def on_query_level1_quote(self, session_id, request_id, level1_quote, error_info):
        print("on_query_level1_quote: { session_id = %d, request_id = %d }"
              % (int(session_id), int(request_id)))

        if error_info and error_info.error_code_type != 0:
            print("  error: { error_code_type = %d, error_code = %d, error_msg = %s }"
                  % (int(error_info.error_code_type), int(error_info.error_code), error_info.error_msg))
            return

        if level1_quote:
            self._print_level1_quote(level1_quote)

    @_cb_guard("on_query_all_level1_quote")
    def on_query_all_level1_quote(self, session_id, request_id, level1_quotes, is_last, error_info):
        data_count = level1_quotes.data_count() if level1_quotes else 0
        print("on_query_all_level1_quote: { session_id = %d, request_id = %d, data_count = %d, is_last = %d }"
              % (int(session_id), int(request_id), int(data_count), int(is_last)))

        if error_info and error_info.error_code_type != 0:
            print("  error: { error_code_type = %d, error_code = %d, error_msg = %s }"
                  % (int(error_info.error_code_type), int(error_info.error_code), error_info.error_msg))
            return

        for i in range(int(data_count)):
            quote = level1_quotes.get(i)
            if quote:
                print("  [%d]:" % i)
                self._print_level1_quote(quote)

    def _print_level1_quote(self, level1_quote):
        """Print a single xlt_level1_quote_t, dispatching by quoteMsgType.

        Output format matches C++ demo ``print_level1_quote`` exactly.
        """
        mt = int(level1_quote.quoteMsgType)

        if mt == int(xlt.SSE_L1_INDEX):
            q = level1_quote.l1_quote.sse_l1_index
            print("    sse_l1_index: { security_code = %s, security_type = %d, sending_time = %d, trad_ses_mode = %d, "
                  "trade_date = %d, last_update_time = %d, md_stream_id = %s, "
                  "prev_close_px = %d, total_volume_traded = %d, num_trades = %d, total_value_traded = %d, "
                  "trading_phase_code = %s, last_px = %d, open_px = %d, close_px = %d, high_px = %d, low_px = %d }"
                  % (q.get_security_code(), int(q.security_type),
                     int(q.sending_time), int(q.trad_ses_mode),
                     int(q.trade_date), int(q.last_update_time), q.md_stream_id,
                     int(q.prev_close_px), int(q.total_volume_traded),
                     int(q.num_trades), int(q.total_value_traded),
                     q.trading_phase_code,
                     int(q.last_px), int(q.open_px),
                     int(q.close_px), int(q.high_px), int(q.low_px)))

        elif mt == int(xlt.SSE_L1_SNAPSHOT):
            q = level1_quote.l1_quote.sse_l1_snapshot
            print("    sse_l1_snapshot: { security_code = %s, security_type = %d, sending_time = %d, trad_ses_mode = %d, "
                  "trade_date = %d, last_update_time = %d, md_stream_id = %s, "
                  "prev_close_px = %d, total_volume_traded = %d, num_trades = %d, total_value_traded = %d, "
                  "trading_phase_code = %s, last_px = %d, open_px = %d, close_px = %d, settle_px = %d, "
                  "high_px = %d, low_px = %d, pre_iopv = %d, option_ref_px = %d, "
                  "option_notional_amount = %d, pre_settle_px = %d, option_open_qty = %d }"
                  % (q.get_security_code(), int(q.security_type),
                     int(q.sending_time), int(q.trad_ses_mode),
                     int(q.trade_date), int(q.last_update_time), q.md_stream_id,
                     int(q.prev_close_px), int(q.total_volume_traded),
                     int(q.num_trades), int(q.total_value_traded),
                     q.trading_phase_code,
                     int(q.last_px), int(q.open_px),
                     int(q.close_px), int(q.settle_px),
                     int(q.high_px), int(q.low_px),
                     int(q.pre_iopv), int(q.option_ref_px),
                     int(q.option_notional_amount), int(q.pre_settle_px),
                     int(q.option_open_qty)))
            for i in range(5):
                print("      bid[%d]: { px = %d, volume = %d }, offer[%d]: { px = %d, volume = %d }"
                      % (i, int(q.get_bid_px(i)), int(q.get_bid_volume(i)),
                         i, int(q.get_offer_px(i)), int(q.get_offer_volume(i))))

        elif mt == int(xlt.SZSE_L1_INDEX):
            q = level1_quote.l1_quote.szse_l1_index
            print("    szse_l1_index: { security_code = %s, channel_no = %d, orig_time = %d, "
                  "md_stream_id = %s, security_id_source = %s, trading_phase_code = %s, "
                  "prev_close_px = %d, num_trades = %d, total_volume_trade = %d, total_value_trade = %d, "
                  "last_index = %d, prev_close_index = %d, open_index = %d, high_index = %d, low_index = %d, close_index = %d }"
                  % (q.get_security_code(), int(q.channel_no), int(q.orig_time),
                     q.md_stream_id, q.security_id_source, q.trading_phase_code,
                     int(q.prev_close_px), int(q.num_trades),
                     int(q.total_volume_trade), int(q.total_value_trade),
                     int(q.last_index), int(q.prev_close_index),
                     int(q.open_index), int(q.high_index),
                     int(q.low_index), int(q.close_index)))

        elif mt == int(xlt.SZSE_L1_SNAPSHOT):
            q = level1_quote.l1_quote.szse_l1_snapshot
            print("    szse_l1_snapshot: { security_code = %s, channel_no = %d, orig_time = %d, "
                  "md_stream_id = %s, security_id_source = %s, trading_phase_code = %s, "
                  "prev_close_px = %d, num_trades = %d, total_volume_trade = %d, total_value_trade = %d, "
                  "last_px = %d, open_px = %d, high_px = %d, low_px = %d, "
                  "px_gain = %d, px_gain_2 = %d, "
                  "weighted_avg_bid_px = %d, total_bid_qty = %d, weighted_avg_offer_px = %d, total_offer_qty = %d, "
                  "pe_ratio_1 = %d, pe_ratio_2 = %d, nav = %d, iopv = %d, warrant_premium = %d, "
                  "limit_up_px = %d, limit_down_px = %d, option_open_qty = %d, option_break_px = %d }"
                  % (q.get_security_code(), int(q.channel_no), int(q.orig_time),
                     q.md_stream_id, q.security_id_source, q.trading_phase_code,
                     int(q.prev_close_px), int(q.num_trades),
                     int(q.total_volume_trade), int(q.total_value_trade),
                     int(q.last_px), int(q.open_px),
                     int(q.high_px), int(q.low_px),
                     int(q.px_gain), int(q.px_gain_2),
                     int(q.weighted_avg_bid_px), int(q.total_bid_qty),
                     int(q.weighted_avg_offer_px), int(q.total_offer_qty),
                     int(q.pe_ratio_1), int(q.pe_ratio_2),
                     int(q.nav), int(q.iopv), int(q.warrant_premium),
                     int(q.limit_up_px), int(q.limit_down_px),
                     int(q.option_open_qty), int(q.option_break_px)))
            for i in range(5):
                print("      bid[%d]: { px = %d, volume = %d }, offer[%d]: { px = %d, volume = %d }"
                      % (i, int(q.get_bid_px(i)), int(q.get_bid_volume(i)),
                         i, int(q.get_offer_px(i)), int(q.get_offer_volume(i))))

        elif mt == int(xlt.SZSE_L1_BOND_SNAPSHOT):
            q = level1_quote.l1_quote.szse_l1_snapshot_bond
            print("    szse_l1_snapshot_bond: { security_code = %s, channel_no = %d, orig_time = %d, "
                  "md_stream_id = %s, security_id_source = %s, trading_phase_code = %s, "
                  "prev_close_px = %d, num_trades = %d, total_volume_trade = %d, total_value_trade = %d, "
                  "last_px = %d, open_px = %d, close_px = %d, high_px = %d, low_px = %d, "
                  "weighted_avg_px = %d, px_gain = %d, px_gain_2 = %d, "
                  "weighted_avg_bid_px = %d, total_bid_qty = %d, weighted_avg_offer_px = %d, total_offer_qty = %d, "
                  "weighted_avg_ir_gain = %d, weighted_avg_prev_ir = %d, "
                  "match_last_px = %d, auction_volume_trade = %d, auction_value_trade = %d }"
                  % (q.get_security_code(), int(q.channel_no), int(q.orig_time),
                     q.md_stream_id, q.security_id_source, q.trading_phase_code,
                     int(q.prev_close_px), int(q.num_trades),
                     int(q.total_volume_trade), int(q.total_value_trade),
                     int(q.last_px), int(q.open_px),
                     int(q.close_px), int(q.high_px), int(q.low_px),
                     int(q.weighted_avg_px), int(q.px_gain), int(q.px_gain_2),
                     int(q.weighted_avg_bid_px), int(q.total_bid_qty),
                     int(q.weighted_avg_offer_px), int(q.total_offer_qty),
                     int(q.weighted_avg_ir_gain), int(q.weighted_avg_prev_ir),
                     int(q.match_last_px), int(q.auction_volume_trade),
                     int(q.auction_value_trade)))
            for i in range(5):
                print("      bid[%d]: { px = %d, volume = %d }, offer[%d]: { px = %d, volume = %d }"
                      % (i, int(q.get_bid_px(i)), int(q.get_bid_volume(i)),
                         i, int(q.get_offer_px(i)), int(q.get_offer_volume(i))))

        else:
            print("    unknown quote_msg_type: { type = %d }" % mt)

    @_cb_guard("on_request_rebuild_tick_quote_error")
    def on_request_rebuild_tick_quote_error(self, session_id, request_id, error_info):
        if error_info:
            print("on_request_rebuild_tick_quote failed: { error_code_type = %d, error_code = %d, error_msg = %s, session_id = %d, request_id = %d }"
                  % (int(error_info.error_code_type), int(error_info.error_code), error_info.error_msg,
                     int(session_id), int(request_id)))

    @_cb_guard("on_rebuild_tick_quote")
    def on_rebuild_tick_quote(self, session_id, request_id, exchange_index, msg_type, tick_quote):
        mt = int(msg_type)
        if mt == int(xlt.SSE_L2_TICK):
            t = tick_quote.sse_l2_tick
            print("rebuild_sse_l2_tick: { security_code = %s, biz_index = %d, channel = %d, tick_time = %d, "
                  "tick_type = %s, buy_order_no = %d, sell_order_no = %d, price = %d, qty = %d, "
                  "trade_money = %d, tick_bs_flag = %s }"
                  % (t.security_code, int(t.biz_index), int(t.channel), int(t.tick_time),
                     str(t.tick_type), int(t.buy_order_no), int(t.sell_order_no), int(t.price), int(t.qty),
                     int(t.trade_money), t.tick_bs_flag))
        elif mt == int(xlt.SSE_L2_BOND_TICK):
            t = tick_quote.sse_l2_bond_tick
            print("rebuild_sse_l2_bond_tick: { security_code = %s, tick_index = %d, channel = %d, tick_time = %d, "
                  "tick_type = %s, buy_order_no = %d, sell_order_no = %d, price = %d, qty = %d, "
                  "trade_money = %d, tick_bs_flag = %s }"
                  % (t.security_code, int(t.tick_index), int(t.channel), int(t.tick_time),
                     str(t.tick_type), int(t.buy_order_no), int(t.sell_order_no), int(t.price), int(t.qty),
                     int(t.trade_money), t.tick_bs_flag))
        elif mt == int(xlt.SZSE_L2_ORDER):
            o = tick_quote.szse_l2_order
            print("rebuild_szse_l2_order: { security_code = %s, channel_no = %d, appl_seq_num = %d, "
                  "price = %d, qty = %d, side = %s, transact_time = %d, ord_type = %s }"
                  % (o.security_code, int(o.channel_no), int(o.appl_seq_num),
                     int(o.price), int(o.qty), str(o.side), int(o.transact_time), str(o.ord_type)))
        elif mt == int(xlt.SZSE_L2_TRADE):
            t = tick_quote.szse_l2_trade
            print("rebuild_szse_l2_trade: { security_code = %s, channel_no = %d, appl_seq_num = %d, "
                  "bid_appl_seq_num = %d, offer_appl_seq_num = %d, price = %d, qty = %d, "
                  "exec_type = %s, transact_time = %d }"
                  % (t.security_code, int(t.channel_no), int(t.appl_seq_num),
                     int(t.bid_appl_seq_num), int(t.offer_appl_seq_num), int(t.price), int(t.qty),
                     str(t.exec_type), int(t.transact_time)))
        else:
            print("rebuild_tick_quote unknown: { msg_type = %d, exchange_index = %d }" % (mt, int(exchange_index)))

    # NOTE: on_quote is optional; the C++ demo doesn't use it for EFH decoding.
    @_cb_guard("on_quote")
    def on_quote(self, mc_type: int, msg_data, msg_len: int):
        dec = xlt.EFHQuoteDecoder(int(mc_type), msg_data, int(msg_len))
        while True:
            item = dec.get_next()
            if not item:
                break
            dt = int(item.data_type)
            if dt == int(xlt.SSE_LEV2_SNAP_MSG_TYPE):
                self.on_report_efh_sse_lev2_snap(item.data.sse_lev2_snap)
            elif dt == int(xlt.SSE_LEV2_TICK_MERGE_MSG_TYPE):
                self.on_report_efh_sse_lev2_tick_merge(item.data.sse_lev2_tick_merge)
            elif dt == int(xlt.SSE_LEV2_IDX_MSG_TYPE):
                self.on_report_efh_sse_lev2_idx(item.data.sse_lev2_idx)
            elif dt == int(xlt.SSE_LEV2_BOND_SNAP_MSG_TYPE):
                self.on_report_efh_sse_lev2_bond_snap(item.data.sse_lev2_bond_snap)
            elif dt == int(xlt.SSE_LEV2_BOND_TICK_MSG_TYPE):
                self.on_report_efh_sse_lev2_bond_tick(item.data.sse_lev2_bond_tick)
            elif dt == int(xlt.SZE_LEV2_SNAP_MSG_TYPE):
                self.on_report_efh_sze_lev2_snap(item.data.sze_lev2_snap)
            elif dt == int(xlt.SZE_LEV2_ORDER_MSG_TYPE):
                self.on_report_efh_sze_lev2_tick(dt, item.data.sze_lev2_order, None)
            elif dt == int(xlt.SZE_LEV2_EXE_MSG_TYPE):
                self.on_report_efh_sze_lev2_tick(dt, None, item.data.sze_lev2_exe)
            elif dt == int(xlt.SZE_LEV2_IDX_MSG_TYPE):
                self.on_report_efh_sze_lev2_idx(item.data.sze_lev2_idx)
            elif dt == int(xlt.SZE_LEV2_BOND_SNAP_MSG_TYPE):
                self.on_report_efh_sze_lev2_bond_snap(item.data.sze_lev2_bond_snap)
            elif dt == int(xlt.SZE_LEV2_BOND_ORDER_MSG_TYPE):
                self.on_report_efh_sze_lev2_bond_tick(dt, item.data.sze_lev2_bond_order, None)
            elif dt == int(xlt.SZE_LEV2_BOND_EXE_MSG_TYPE):
                self.on_report_efh_sze_lev2_bond_tick(dt, None, item.data.sze_lev2_bond_exe)
            else:
                continue

        xlt_dec = xlt.XLTQuoteDecoder(int(mc_type), msg_data, int(msg_len))
        while True:
            item = xlt_dec.get_next()
            if not item:
                break
            dt = int(item.data_type)
            if dt == xlt.SSE_L1_INDEX:
                self.on_sse_l1_index(item.data.sse_l1_index)
            elif dt == xlt.SSE_L1_SNAPSHOT:
                self.on_sse_l1_snapshot(item.data.sse_l1_snapshot)
            elif dt == xlt.SZSE_L1_INDEX:
                self.on_szse_l1_index(item.data.szse_l1_index)
            elif dt == xlt.SZSE_L1_SNAPSHOT:
                self.on_szse_l1_snapshot(item.data.szse_l1_snapshot)
            elif dt == xlt.SZSE_L1_BOND_SNAPSHOT:
                self.on_szse_l1_snapshot_bond(item.data.szse_l1_snapshot_bond)
            else:
                continue

    # ---- XLight L1 callbacks (match C++ printf format) ----

    @_cb_guard("on_sse_l1_index")
    def on_sse_l1_index(self, idx):
        if not idx:
            return
        print("on_sse_l1_index: { security_code = %s, security_type = %d, sending_time = %d, trad_ses_mode = %d, "
              "trade_date = %d, last_update_time = %d, md_stream_id = %s, "
              "prev_close_px = %d, total_volume_traded = %d, num_trades = %d, total_value_traded = %d, "
              "trading_phase_code = %s, last_px = %d, open_px = %d, close_px = %d, high_px = %d, low_px = %d }"
              % (idx.get_security_code(), int(idx.security_type),
                 int(idx.sending_time), int(idx.trad_ses_mode),
                 int(idx.trade_date), int(idx.last_update_time), idx.md_stream_id,
                 int(idx.prev_close_px), int(idx.total_volume_traded),
                 int(idx.num_trades), int(idx.total_value_traded),
                 idx.trading_phase_code,
                 int(idx.last_px), int(idx.open_px),
                 int(idx.close_px), int(idx.high_px), int(idx.low_px)))

    @_cb_guard("on_sse_l1_snapshot")
    def on_sse_l1_snapshot(self, snap):
        if not snap:
            return
        print("on_sse_l1_snapshot: { security_code = %s, security_type = %d, sending_time = %d, trad_ses_mode = %d, "
              "trade_date = %d, last_update_time = %d, md_stream_id = %s, "
              "prev_close_px = %d, total_volume_traded = %d, num_trades = %d, total_value_traded = %d, "
              "trading_phase_code = %s, last_px = %d, open_px = %d, close_px = %d, settle_px = %d, "
              "high_px = %d, low_px = %d, pre_iopv = %d, option_ref_px = %d, "
              "option_notional_amount = %d, pre_settle_px = %d, option_open_qty = %d }"
              % (snap.get_security_code(), int(snap.security_type),
                 int(snap.sending_time), int(snap.trad_ses_mode),
                 int(snap.trade_date), int(snap.last_update_time), snap.md_stream_id,
                 int(snap.prev_close_px), int(snap.total_volume_traded),
                 int(snap.num_trades), int(snap.total_value_traded),
                 snap.trading_phase_code,
                 int(snap.last_px), int(snap.open_px),
                 int(snap.close_px), int(snap.settle_px),
                 int(snap.high_px), int(snap.low_px),
                 int(snap.pre_iopv), int(snap.option_ref_px),
                 int(snap.option_notional_amount), int(snap.pre_settle_px),
                 int(snap.option_open_qty)))
        for i in range(5):
            print("  bid[%d]: { px = %d, volume = %d }, offer[%d]: { px = %d, volume = %d }"
                  % (i, int(snap.get_bid_px(i)), int(snap.get_bid_volume(i)),
                     i, int(snap.get_offer_px(i)), int(snap.get_offer_volume(i))))

    @_cb_guard("on_szse_l1_index")
    def on_szse_l1_index(self, idx):
        if not idx:
            return
        print("on_szse_l1_index: { security_code = %s, channel_no = %d, orig_time = %d, "
              "md_stream_id = %s, security_id_source = %s, trading_phase_code = %s, "
              "prev_close_px = %d, num_trades = %d, total_volume_trade = %d, total_value_trade = %d, "
              "last_index = %d, prev_close_index = %d, open_index = %d, high_index = %d, low_index = %d, close_index = %d }"
              % (idx.get_security_code(), int(idx.channel_no), int(idx.orig_time),
                 idx.md_stream_id, idx.security_id_source, idx.trading_phase_code,
                 int(idx.prev_close_px), int(idx.num_trades),
                 int(idx.total_volume_trade), int(idx.total_value_trade),
                 int(idx.last_index), int(idx.prev_close_index),
                 int(idx.open_index), int(idx.high_index),
                 int(idx.low_index), int(idx.close_index)))

    @_cb_guard("on_szse_l1_snapshot")
    def on_szse_l1_snapshot(self, snap):
        if not snap:
            return
        print("on_szse_l1_snapshot: { security_code = %s, channel_no = %d, orig_time = %d, "
              "md_stream_id = %s, security_id_source = %s, trading_phase_code = %s, "
              "prev_close_px = %d, num_trades = %d, total_volume_trade = %d, total_value_trade = %d, "
              "last_px = %d, open_px = %d, high_px = %d, low_px = %d, "
              "px_gain = %d, px_gain_2 = %d, "
              "weighted_avg_bid_px = %d, total_bid_qty = %d, weighted_avg_offer_px = %d, total_offer_qty = %d, "
              "pe_ratio_1 = %d, pe_ratio_2 = %d, nav = %d, iopv = %d, warrant_premium = %d, "
              "limit_up_px = %d, limit_down_px = %d, option_open_qty = %d, option_break_px = %d }"
              % (snap.get_security_code(), int(snap.channel_no), int(snap.orig_time),
                 snap.md_stream_id, snap.security_id_source, snap.trading_phase_code,
                 int(snap.prev_close_px), int(snap.num_trades),
                 int(snap.total_volume_trade), int(snap.total_value_trade),
                 int(snap.last_px), int(snap.open_px),
                 int(snap.high_px), int(snap.low_px),
                 int(snap.px_gain), int(snap.px_gain_2),
                 int(snap.weighted_avg_bid_px), int(snap.total_bid_qty),
                 int(snap.weighted_avg_offer_px), int(snap.total_offer_qty),
                 int(snap.pe_ratio_1), int(snap.pe_ratio_2),
                 int(snap.nav), int(snap.iopv), int(snap.warrant_premium),
                 int(snap.limit_up_px), int(snap.limit_down_px),
                 int(snap.option_open_qty), int(snap.option_break_px)))
        for i in range(5):
            print("  bid[%d]: { px = %d, volume = %d }, offer[%d]: { px = %d, volume = %d }"
                  % (i, int(snap.get_bid_px(i)), int(snap.get_bid_volume(i)),
                     i, int(snap.get_offer_px(i)), int(snap.get_offer_volume(i))))

    @_cb_guard("on_szse_l1_snapshot_bond")
    def on_szse_l1_snapshot_bond(self, bond):
        if not bond:
            return
        print("on_szse_l1_snapshot_bond: { security_code = %s, channel_no = %d, orig_time = %d, "
              "md_stream_id = %s, security_id_source = %s, trading_phase_code = %s, "
              "prev_close_px = %d, num_trades = %d, total_volume_trade = %d, total_value_trade = %d, "
              "last_px = %d, open_px = %d, close_px = %d, high_px = %d, low_px = %d, "
              "weighted_avg_px = %d, px_gain = %d, px_gain_2 = %d, "
              "weighted_avg_bid_px = %d, total_bid_qty = %d, weighted_avg_offer_px = %d, total_offer_qty = %d, "
              "weighted_avg_ir_gain = %d, weighted_avg_prev_ir = %d, "
              "match_last_px = %d, auction_volume_trade = %d, auction_value_trade = %d }"
              % (bond.get_security_code(), int(bond.channel_no), int(bond.orig_time),
                 bond.md_stream_id, bond.security_id_source, bond.trading_phase_code,
                 int(bond.prev_close_px), int(bond.num_trades),
                 int(bond.total_volume_trade), int(bond.total_value_trade),
                 int(bond.last_px), int(bond.open_px),
                 int(bond.close_px), int(bond.high_px), int(bond.low_px),
                 int(bond.weighted_avg_px), int(bond.px_gain), int(bond.px_gain_2),
                 int(bond.weighted_avg_bid_px), int(bond.total_bid_qty),
                 int(bond.weighted_avg_offer_px), int(bond.total_offer_qty),
                 int(bond.weighted_avg_ir_gain), int(bond.weighted_avg_prev_ir),
                 int(bond.match_last_px), int(bond.auction_volume_trade),
                 int(bond.auction_value_trade)))
        for i in range(5):
            print("  bid[%d]: { px = %d, volume = %d }, offer[%d]: { px = %d, volume = %d }"
                  % (i, int(bond.get_bid_px(i)), int(bond.get_bid_volume(i)),
                     i, int(bond.get_offer_px(i)), int(bond.get_offer_volume(i))))

    # ---- EFH HPF callbacks (match C++ printf format) ----

    @_cb_guard("on_report_efh_sse_lev2_snap")
    def on_report_efh_sse_lev2_snap(self, p_snap):
        if not p_snap:
            return
        print("sse_l2_snap: { symbol = %s, quote_update_time = %d, last_price = %d, total_quantity = %d, total_value = %d }"
              % (p_snap.get_symbol(), int(p_snap.m_quote_update_time), int(p_snap.m_last_price),
                 int(p_snap.m_total_quantity), int(p_snap.m_total_value)))

    @_cb_guard("on_report_efh_sse_lev2_tick_merge")
    def on_report_efh_sse_lev2_tick_merge(self, p_tick):
        if not p_tick:
            return
        print("sse_l2_tick_merge: { symbol = %s, tick_time = %d, tick_index = %d, tick_type = %s, price = %d, quantity = %d }"
              % (p_tick.get_symbol(), int(p_tick.m_tick_time), int(p_tick.m_tick_index),
                 p_tick.get_tick_type(), int(p_tick.m_price), int(p_tick.m_quantity)))

    @_cb_guard("on_report_efh_sse_lev2_idx")
    def on_report_efh_sse_lev2_idx(self, p_idx):
        if not p_idx:
            return
        print("sse_l2_idx: { symbol = %s, quote_update_time = %d, last_price = %d, total_quantity = %d, total_value = %d }"
              % (p_idx.get_symbol(), int(p_idx.m_quote_update_time), int(p_idx.m_last_price),
                 int(p_idx.m_total_quantity), int(p_idx.m_total_value)))

    @_cb_guard("on_report_efh_sse_lev2_bond_snap")
    def on_report_efh_sse_lev2_bond_snap(self, p_bond_snap):
        if not p_bond_snap:
            return
        print("sse_l2_bond_snap: { symbol = %s, quote_update_time = %d, last_price = %d, total_quantity = %d, total_value = %d }"
              % (p_bond_snap.get_symbol(), int(p_bond_snap.m_quote_update_time), int(p_bond_snap.m_last_price),
                 int(p_bond_snap.m_total_quantity), int(p_bond_snap.m_total_value)))

    @_cb_guard("on_report_efh_sse_lev2_bond_tick")
    def on_report_efh_sse_lev2_bond_tick(self, p_bond_tick):
        if not p_bond_tick:
            return
        print("sse_l2_bond_tick: { symbol = %s, tick_time = %d, tick_index = %d, tick_type = %s, price = %d, quantity = %d }"
              % (p_bond_tick.get_symbol(), int(p_bond_tick.m_tick_time), int(p_bond_tick.m_tick_index),
                 p_bond_tick.get_tick_type(), int(p_bond_tick.m_price), int(p_bond_tick.m_quantity)))

    @_cb_guard("on_report_efh_sze_lev2_snap")
    def on_report_efh_sze_lev2_snap(self, p_snap):
        if not p_snap:
            return
        h = p_snap.get_head()
        print("sze_l2_snap: { symbol = %s, sequence = %d, message_type = %d, security_type = %d, "
              "quote_update_time = %d, channel_num = %d, sequence_num = %d, "
              "trading_status = %d, total_trade_num = %d, total_quantity = %d, total_value = %d, "
              "pre_close_price = %d, last_price = %d, open_price = %d, day_high_price = %d, day_low_price = %d, "
              "today_close_price = %d, total_bid_weighted_avg_price = %d, total_bid_quantity = %d, "
              "total_ask_weighted_avg_price = %d, total_ask_quantity = %d, "
              "lpv = %d, iopv = %d, upper_limit_price = %d, low_limit_price = %d, open_interest = %d }"
              % (h.get_symbol(), int(h.m_sequence), int(h.m_message_type), int(h.m_security_type),
                 int(h.m_quote_update_time), int(h.m_channel_num), int(h.m_sequence_num),
                 int(p_snap.m_trading_status), int(p_snap.m_total_trade_num), int(p_snap.m_total_quantity), int(p_snap.m_total_value),
                 int(p_snap.m_pre_close_price), int(p_snap.m_last_price), int(p_snap.m_open_price),
                 int(p_snap.m_day_high_price), int(p_snap.m_day_low_price),
                 int(p_snap.m_today_close_price), int(p_snap.m_total_bid_weighted_avg_price), int(p_snap.m_total_bid_quantity),
                 int(p_snap.m_total_ask_weighted_avg_price), int(p_snap.m_total_ask_quantity),
                 int(p_snap.m_lpv), int(p_snap.m_iopv), int(p_snap.m_upper_limit_price),
                 int(p_snap.m_low_limit_price), int(p_snap.m_open_interest)))
        for i in range(10):
            print("  bid[%d]: { price = %d, quantity = %d }, ask[%d]: { price = %d, quantity = %d }"
                  % (i, int(p_snap.get_bid_unit(i).m_price), int(p_snap.get_bid_unit(i).m_quantity),
                     i, int(p_snap.get_ask_unit(i).m_price), int(p_snap.get_ask_unit(i).m_quantity)))

    @_cb_guard("on_report_efh_sze_lev2_tick")
    def on_report_efh_sze_lev2_tick(self, msg_type, p_order, p_exe):
        if msg_type == xlt.SZE_LEV2_ORDER_MSG_TYPE and p_order:
            h = p_order.get_head()
            print("sze_l2_order: { symbol = %s, sequence = %d, message_type = %d, security_type = %d, "
                  "quote_update_time = %d, channel_num = %d, sequence_num = %d, "
                  "order_price = %d, order_quantity = %d, side_flag = %s, order_type = %s }"
                  % (h.get_symbol(), int(h.m_sequence), int(h.m_message_type), int(h.m_security_type),
                     int(h.m_quote_update_time), int(h.m_channel_num), int(h.m_sequence_num),
                     int(p_order.m_order_price), int(p_order.m_order_quantity),
                     chr(p_order.m_side_flag), chr(p_order.m_order_type)))
        elif msg_type == xlt.SZE_LEV2_EXE_MSG_TYPE and p_exe:
            h = p_exe.get_head()
            print("sze_l2_exe: { symbol = %s, sequence = %d, message_type = %d, security_type = %d, "
                  "quote_update_time = %d, channel_num = %d, sequence_num = %d, "
                  "trade_buy_num = %d, trade_sell_num = %d, trade_price = %d, trade_quantity = %d, trade_type = %s }"
                  % (h.get_symbol(), int(h.m_sequence), int(h.m_message_type), int(h.m_security_type),
                     int(h.m_quote_update_time), int(h.m_channel_num), int(h.m_sequence_num),
                     int(p_exe.m_trade_buy_num), int(p_exe.m_trade_sell_num),
                     int(p_exe.m_trade_price), int(p_exe.m_trade_quantity), chr(p_exe.m_trade_type)))

    @_cb_guard("on_report_efh_sze_lev2_idx")
    def on_report_efh_sze_lev2_idx(self, p_idx):
        if not p_idx:
            return
        h = p_idx.get_head()
        print("sze_l2_idx: { symbol = %s, sequence = %d, message_type = %d, security_type = %d, "
              "quote_update_time = %d, channel_num = %d, sequence_num = %d, "
              "total_trade_num = %d, total_quantity = %d, total_value = %d, "
              "last_price = %d, pre_close_price = %d, open_price = %d, day_high_price = %d, day_low_price = %d, today_close_price = %d }"
              % (h.get_symbol(), int(h.m_sequence), int(h.m_message_type), int(h.m_security_type),
                 int(h.m_quote_update_time), int(h.m_channel_num), int(h.m_sequence_num),
                 int(p_idx.m_total_trade_num), int(p_idx.m_total_quantity), int(p_idx.m_total_value),
                 int(p_idx.m_last_price), int(p_idx.m_pre_close_price), int(p_idx.m_open_price),
                 int(p_idx.m_day_high_price), int(p_idx.m_day_low_price), int(p_idx.m_today_close_price)))

    @_cb_guard("on_report_efh_sze_lev2_bond_snap")
    def on_report_efh_sze_lev2_bond_snap(self, p_bond_snap):
        if not p_bond_snap:
            return
        h = p_bond_snap.get_head()
        print("sze_l2_bond_snap: { symbol = %s, sequence = %d, message_type = %d, security_type = %d, "
              "quote_update_time = %d, channel_num = %d, sequence_num = %d, "
              "trading_status = %d, total_trade_num = %d, total_quantity = %d, total_value = %d, "
              "pre_close_price = %d, last_price = %d, open_price = %d, day_high_price = %d, day_low_price = %d, "
              "today_close_price = %d, total_trade_weighted_avg_price = %d, "
              "fluctuation_1 = %d, fluctuation_2 = %d, "
              "total_bid_weighted_avg_price = %d, total_bid_quantity = %d, "
              "total_ask_weighted_avg_price = %d, total_ask_quantity = %d, "
              "weighted_avg_price_BP = %d, weighted_avg_pre_close_price = %d, auction_trade_last_price = %d, "
              "auction_volume_trade = %d, auction_value_trade = %d }"
              % (h.get_symbol(), int(h.m_sequence), int(h.m_message_type), int(h.m_security_type),
                 int(h.m_quote_update_time), int(h.m_channel_num), int(h.m_sequence_num),
                 int(p_bond_snap.m_trading_status), int(p_bond_snap.m_total_trade_num),
                 int(p_bond_snap.m_total_quantity), int(p_bond_snap.m_total_value),
                 int(p_bond_snap.m_pre_close_price), int(p_bond_snap.m_last_price),
                 int(p_bond_snap.m_open_price), int(p_bond_snap.m_day_high_price), int(p_bond_snap.m_day_low_price),
                 int(p_bond_snap.m_today_close_price), int(p_bond_snap.m_total_trade_weighted_avg_price),
                 int(p_bond_snap.m_fluctuation_1), int(p_bond_snap.m_fluctuation_2),
                 int(p_bond_snap.m_total_bid_weighted_avg_price), int(p_bond_snap.m_total_bid_quantity),
                 int(p_bond_snap.m_total_ask_weighted_avg_price), int(p_bond_snap.m_total_ask_quantity),
                 int(p_bond_snap.m_weighted_avg_price_BP), int(p_bond_snap.m_weighted_avg_pre_close_price),
                 int(p_bond_snap.m_auction_trade_last_price),
                 int(p_bond_snap.m_auction_volume_trade), int(p_bond_snap.m_auction_value_trade)))
        for i in range(10):
            print("  bid[%d]: { price = %d, quantity = %d }, ask[%d]: { price = %d, quantity = %d }"
                  % (i, int(p_bond_snap.get_bid_unit(i).m_price), int(p_bond_snap.get_bid_unit(i).m_quantity),
                     i, int(p_bond_snap.get_ask_unit(i).m_price), int(p_bond_snap.get_ask_unit(i).m_quantity)))

    @_cb_guard("on_report_efh_sze_lev2_bond_tick")
    def on_report_efh_sze_lev2_bond_tick(self, msg_type, p_order, p_exe):
        if msg_type == xlt.SZE_LEV2_BOND_ORDER_MSG_TYPE and p_order:
            h = p_order.get_head()
            print("sze_l2_bond_order: { symbol = %s, sequence = %d, message_type = %d, security_type = %d, "
                  "quote_update_time = %d, channel_num = %d, sequence_num = %d, "
                  "price = %d, quantity = %d, side = %s, order_type = %s }"
                  % (h.get_symbol(), int(h.m_sequence), int(h.m_message_type), int(h.m_security_type),
                     int(h.m_quote_update_time), int(h.m_channel_num), int(h.m_sequence_num),
                     int(p_order.m_price), int(p_order.m_quantity),
                     chr(p_order.m_side), chr(p_order.m_order_type)))
        elif msg_type == xlt.SZE_LEV2_BOND_EXE_MSG_TYPE and p_exe:
            h = p_exe.get_head()
            print("sze_l2_bond_exe: { symbol = %s, sequence = %d, message_type = %d, security_type = %d, "
                  "quote_update_time = %d, channel_num = %d, sequence_num = %d, "
                  "buy_num = %d, sell_num = %d, price = %d, quantity = %d, type = %s }"
                  % (h.get_symbol(), int(h.m_sequence), int(h.m_message_type), int(h.m_security_type),
                     int(h.m_quote_update_time), int(h.m_channel_num), int(h.m_sequence_num),
                     int(p_exe.m_buy_num), int(p_exe.m_sell_num),
                     int(p_exe.m_price), int(p_exe.m_quantity), chr(p_exe.m_type)))

