"""
@file runme.py
@author 上海前路有光数字科技
@date 2024-03-15
@brief XLTApi Python版使用示例主程序
本程序演示如何使用XLTApi进行初始化、登录、下单等操作。
用户可参考本程序，结合自己的业务需求，开发自己的交易程序。
"""

import sys
import time
import threading
import logging
import py_xlt_api as xlight
from trade_spi import MySpi
from quote_spi import MyQuoteSpi

# 配置日志
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

# ============== 全局变量 ==============
my_api: xlight.XLTApi = xlight.XLTApi.create()   # API实例，全局唯一
global_session_id = 0                            # 会话ID
global_login_success = False                     # 是否登录成功标识
global_login_finished = threading.Event()        # 已建立会话标识
global_request_id = 0                            # 全局请求id


def next_request_id() -> int:
    """
    【演示】获取下一个请求ID
    XLTApi中使用request_id用于用户在异步回调中定位对应的请求。
    XLTApi所有接口使用到request_id时，不对request_id做任何验证及业务假设，在回调中原样返回给用户。
    此方法用于演示request_id，用户可根据实际业务场景，自定义request_id。
    :return: 下一个请求ID
    """
    global global_request_id
    global_request_id += 1
    return global_request_id


my_spi = MySpi(my_api, next_request_id)          # 交易回调实例
my_quote_spi = MyQuoteSpi()                      # 行情回调实例


def initialize() -> bool:
    """
    【演示】XLTApi初始化

    本方法演示如何配置以及初始化API，步骤如下：
    1、配置API
    2、初始化：设置API配置，以及注册交易回调接口和行情回调接口
    :return: 初始化是否成功
    :remark: XLTApi必须在使用之前进行初始化，并且只能初始化一次
    """
    # 配置
    config = xlight.XLTApiConfig()
    config.set_trading_day(20240320)              # （必填）设置交易日，格式YYYYMMDD，如20230901
    config.set_local_addr(                        # （必填）设置本地网卡IP地址
        "172.16.10.175",                          # agw 连接agw的本地网卡IP
        "172.16.10.175",                          # trade 连接快速报单的本地网卡IP
        "172.16.10.175"                           # quote 接收行情数据的本地网卡IP
    )
    config.add_agw_addr("192.168.5.73", 6060)     # （必填）添加网关地址
    config.set_path(".")                          # （选填）设置API生成文件存放路径
    config.set_log_level(xlight.ApiLogLevel_LOG_TRACE)  # （选填）设置日志级别
    config.set_request_timeout(10)                # （选填）设置请求超时时间，单位秒
    config.set_order_timeout(10)                  # （选填）设置订单超时时间，单位秒
    config.set_order_seq_self_define(False)       # （选填）设置是否启用用户自定义订单序号

    # ====以下为行情专用配置，若您不需要行情，可忽略===================================
    # 配置行情接收，level1(含上海及深圳)，上海level2，深圳level2分别在独立线程运行
    config.receive_quote_in_single_thread(
        xlight.quote_receive_cfg_t(True, -1),       # 启用level1行情
        xlight.quote_receive_cfg_t(True, -1),       # 启用上海level2行情
        xlight.quote_receive_cfg_t(True, -1)        # 启用深圳level2行情
    )

    # 配置行情接收，level1，上海level2快照，上海level2逐笔，深圳level2快照，深圳level2逐笔
    config.receive_quote_in_snap_tick_thread(
        xlight.quote_receive_cfg_t(True, -1),       # level1行情
        xlight.quote_receive_cfg_t(True, -1),       # 上海level2快照
        xlight.quote_receive_cfg_t(True, -1),       # 上海level2逐笔
        xlight.quote_receive_cfg_t(True, -1),       # 深圳level2快照
        xlight.quote_receive_cfg_t(True, -1)        # 深圳level2逐笔
    )

    # 配置行情接收，level1，上海level2各行情类型，深圳level2各行情类型
    sse_l2_config = xlight.quote_l2_receive_cfg_t()
    sse_l2_config.stock_snapshot = xlight.quote_receive_cfg_t(True, -1)
    sse_l2_config.stock_tick = xlight.quote_receive_cfg_t(True, -1)
    sse_l2_config.index = xlight.quote_receive_cfg_t(True, -1)
    sse_l2_config.bond_snapshot = xlight.quote_receive_cfg_t(True, -1)
    sse_l2_config.bond_tick = xlight.quote_receive_cfg_t(True, -1)

    szse_l2_config = xlight.quote_l2_receive_cfg_t()
    szse_l2_config.stock_snapshot = xlight.quote_receive_cfg_t(True, -1)
    szse_l2_config.stock_tick = xlight.quote_receive_cfg_t(True, -1)
    szse_l2_config.index = xlight.quote_receive_cfg_t(True, -1)
    szse_l2_config.bond_snapshot = xlight.quote_receive_cfg_t(True, -1)
    szse_l2_config.bond_tick = xlight.quote_receive_cfg_t(True, -1)

    config.receive_quote_in_channel_per_thread(
        xlight.quote_receive_cfg_t(True, -1),       # level1行情
        sse_l2_config,                            # 上海level2配置
        szse_l2_config                            # 深圳level2配置
    )
    # ==== 行情配置结束 ==============================================================

    # API初始化，在使用之前调用，只需调用一次
    if not my_api.initialize(config, my_spi, my_quote_spi):
        error_info = my_api.get_last_error()
        logging.error(f"error_code: {error_info.error_code}, sub_error_code: {error_info.sub_error_code}, error_msg: {error_info.error_msg}")
        return False

    logging.info("Api initialized successfully!")
    return True


def login() -> bool:
    """
    【演示】登录请求
    本方法演示XLTApi登录过程
    XLTApi登录响应为异步响应，本方法封装为同步过程，用户根据实际业务场景进行调整。
    流程如下：
    1、发起登录请求
    2、等待异步响应
    3、判断是否登录成功，若成功，保存会话ID global_session_id
    后续接口调用都需要用到本次登录返回的global_session_id
    :return: 登录是否成功
    :remark: 登录时，用户可自定义client_id，在本次登录中的所有委托及回报都将打上此标记
    """
    global global_session_id, global_login_success

    if global_session_id != 0:
        my_api.logout(global_session_id)

    global_session_id = 0
    global_login_success = False
    global_login_finished.clear()
    my_spi.login_event.clear()

    # 登录账号信息
    request_id = next_request_id()                      # request_id，用于定位本次请求
    client_id = 1                                       # 客户端标识ID（委托源ID），取值为[0, 255]
    pub_key_base64 = "XXXXXXXXXXXXXX"                   # 用户公钥，v1.5暂未启用
    account_name = "00100000000501"                     # 交易账号
    password = "12345678"                               # 登录密码

    # 登录请求
    result = my_api.login(request_id, client_id, account_name, password, pub_key_base64)
    if not result:
        error_info = my_api.get_last_error()
        logging.error(f"Login failed! error_code: {error_info.error_code}, sub_error_code: {error_info.sub_error_code}, error_msg: {error_info.error_msg}")
        return False

    # 等待登录响应
    my_spi.login_event.wait()
    global_session_id = my_spi.last_login_session_id
    global_login_success = (global_session_id != 0)

    return global_login_success


def subscribe_report() -> bool:
    """
    【演示】订阅交易回报
    订阅交易数据(需要在登录成功后调用)
    :return: 订阅发送是否成功
    :remark: 只有订阅了交易回报，才能收到订单状态更新和成交回报等信息
    """
    if not my_api.subscribe_report(global_session_id, xlight.TradeResumeType_QUICK, 0):
        error_info = my_api.get_last_error()
        logging.error(f"error_code: {error_info.error_code}, sub_error_code: {error_info.sub_error_code}, error_msg: {error_info.error_msg}")
        return False

    logging.info("subscribe request send success!!!")
    return True


def start_quote() -> bool:
    """
    【演示】启动行情
    本方法演示如何启动行情
    结果将从EFHQuoteSpi.on_start_quote回调中返回
    :return: 启动行情是否成功
    :remark: 启动行情前提是在配置中启用了行情接收
    """
    # 订阅全部沪深A股行情
    my_api.subscribe_all_quote(xlight.ExchangeIndex_SH)
    my_api.subscribe_all_quote(xlight.ExchangeIndex_SZ)

    # 订阅指定合约行情，若不需要订阅全部合约，可使用此方法订阅指定合约
    # my_api.subscribe_quote(xlight.ExchangeIndex_SH, "000072")
    # my_api.subscribe_quote(xlight.ExchangeIndex_SZ, "000001")

    # 启动行情接收
    if not my_api.start_quote(global_session_id):
        error_info = my_api.get_last_error()
        logging.error(f"error_code: {error_info.error_code}, sub_error_code: {error_info.sub_error_code}, error_msg: {error_info.error_msg}")
        return False

    logging.info("Start quote!!")
    return True


def query_assets() -> bool:
    """
    【演示】查询资金信息
    本方法演示如何查询资金信息
    结果将从on_query_assets回调中返回
    :return: 查询请求发送是否成功
    """
    if not my_api.query_assets(global_session_id, next_request_id()):
        error_info = my_api.get_last_error()
        logging.error(f"error_code: {error_info.error_code}, sub_error_code: {error_info.sub_error_code}, error_msg: {error_info.error_msg}")
        return False

    logging.info("Query assets info request send success! Wait for response!")
    return True


def query_positions() -> bool:
    """
    【演示】查询持仓信息
    本方法演示如何查询持仓信息
    结果将从on_query_positions回调中返回
    :return: 查询请求发送是否成功
    """
    # 当security_code为空，ExchangeIndex_INIT时，表示查询所有持仓
    if not my_api.query_positions(global_session_id, next_request_id(), "", xlight.ExchangeIndex_INIT, 0):
        error_info = my_api.get_last_error()
        logging.error(f"error_code: {error_info.error_code}, sub_error_code: {error_info.sub_error_code}, error_msg: {error_info.error_msg}")
        return False

    logging.info("Query positions request send success! Wait for response!")
    return True


def query_orders() -> bool:
    """
    【演示】查询订单信息
    本方法演示如何查询订单信息
    结果将从on_query_orders回调中返回
    :return: 查询请求发送是否成功
    """
    # 查询订单（根据条件查询）
    order_query_param = xlight.xlt_order_query_param_t()
    order_query_param.xid = 0                     # 需要查询的委托编号，0表示根据后续条件查询
    order_query_param.security_code = "000001"    # 证券代码

    if not my_api.query_orders(global_session_id, next_request_id(), order_query_param):
        error_info = my_api.get_last_error()
        logging.error(f"error_code: {error_info.error_code}, sub_error_code: {error_info.sub_error_code}, error_msg: {error_info.error_msg}")
        return False

    logging.info("Query orders request send success! Wait for response!")
    return True


def query_orders_by_page() -> bool:
    """
    【演示】分页查询订单信息
    本方法演示如何分页查询订单信息
    结果将从on_query_orders_by_page回调中返回
    :return: 查询请求发送是否成功
    """
    order_query_by_page_param = xlight.xlt_query_by_page_param_t()
    order_query_by_page_param.req_count = 100
    order_query_by_page_param.reference = 0

    if not my_api.query_orders_by_page(global_session_id, next_request_id(), order_query_by_page_param):
        error_info = my_api.get_last_error()
        logging.error(f"error_code: {error_info.error_code}, sub_error_code: {error_info.sub_error_code}, error_msg: {error_info.error_msg}")
        return False

    logging.info("Query orders by page send success! Wait for response!")
    return True


def query_trades() -> bool:
    """
    【演示】查询成交信息
    本方法演示如何查询成交信息
    结果将从on_query_trades回调中返回
    :return: 查询请求发送是否成功
    """
    # 查询成交，xid若填0表示查询所有
    if not my_api.query_trades(global_session_id, next_request_id(), 0):
        error_info = my_api.get_last_error()
        logging.error(f"error_code: {error_info.error_code}, sub_error_code: {error_info.sub_error_code}, error_msg: {error_info.error_msg}")
        return False

    logging.info("Query trades request send success! Wait for response!")
    return True


def insert_order() -> int:
    """
    【演示】插入订单
    本方法演示如何插入订单
    订单响应将从on_order_response、on_trade_report、on_order_error_response、on_order_end回调中返回
    :return: 插入订单请求是否发送成功，返回xid表示发送成功，返回0表示发送失败
    """
    # 订单委托
    order_info = xlight.xlt_order_insert_info_t()
    order_info.security_code = "600010"                       # 证券代码
    order_info.exchange_index = xlight.ExchangeIndex_SH       # 交易市场
    order_info.price = 100000                                 # 委托价格，放大1万倍
    order_info.quantity = 4400                                # 委托数量
    order_info.order_type = xlight.OrderType_LIMIT_PRICE      # 报价类型，限价委托
    order_info.side = xlight.Side_BUY                         # 买卖方向，买入
    order_info.business_type = xlight.BusinessType_CASH       # 业务类型，股票现货

    xid = my_api.insert_order(global_session_id, order_info)
    if xid == 0:
        error_info = my_api.get_last_error()
        logging.error(f"error_code: {error_info.error_code}, sub_error_code: {error_info.sub_error_code}, error_msg: {error_info.error_msg}")
        return 0

    logging.info("Insert order request send success! Wait for response!")
    return xid


def cancel_order(xid: int) -> int:
    """
    【演示】撤单
    本方法演示如何撤单
    撤单响应将从on_cancel_response、on_order_error_response回调中返回
    :param xid: 要撤销的订单xid
    :return: 撤单请求是否发送成功，返回cancel_xid表示发送成功，返回0表示发送失败
    """
    # 撤单
    cancel_order_info = xlight.xlt_cancel_order_info_t()
    cancel_order_info.order_sequence = 0
    cancel_order_info.origin_xid = xid

    cancel_xid = my_api.cancel_order(global_session_id, cancel_order_info)
    if cancel_xid == 0:
        error_info = my_api.get_last_error()
        logging.error(f"error_code: {error_info.error_code}, sub_error_code: {error_info.sub_error_code}, error_msg: {error_info.error_msg}")
        return 0

    logging.info(f"Cancel order request send success! Wait for response! cancel_xid: {cancel_xid}")
    return cancel_xid


def query_static_quote_info() -> bool:
    """
    【演示】查询静态行情信息
    本方法演示如何查询静态行情信息
    结果将从on_query_static_quote_info回调中返回
    :return: 查询请求发送是否成功
    """
    if not my_api.query_static_quote_info(global_session_id, next_request_id()):
        error_info = my_api.get_last_error()
        logging.error(f"error_code: {error_info.error_code}, sub_error_code: {error_info.sub_error_code}, error_msg: {error_info.error_msg}")
        return False

    logging.info("Query static quote info requests request send success! Wait for response!")
    return True


def query_sse_l1_index() -> bool:
    """
    【演示】查询最新上证Level1指数行情信息
    本方法演示如何查询最新上证Level1指数行情信息
    结果将从on_query_sse_l1_index回调中返回
    :return: 查询请求发送是否成功
    """
    if not my_api.query_sse_l1_index(global_session_id, next_request_id(), "000001"):
        error_info = my_api.get_last_error()
        logging.error(f"error_code: {error_info.error_code}, sub_error_code: {error_info.sub_error_code}, error_msg: {error_info.error_msg}")
        return False

    logging.info("Query sse l1 index requests request send success! Wait for response!")
    return True


def query_sse_l1_snapshot() -> bool:
    """
    【演示】查询最新上证Level1快照行情信息
    本方法演示如何查询最新上证Level1快照行情信息
    结果将从on_query_sse_l1_snapshot回调中返回
    :return: 查询请求发送是否成功
    """
    if not my_api.query_sse_l1_snapshot(global_session_id, next_request_id(), "600000"):
        error_info = my_api.get_last_error()
        logging.error(f"error_code: {error_info.error_code}, sub_error_code: {error_info.sub_error_code}, error_msg: {error_info.error_msg}")
        return False

    logging.info("Query sse l1 snapshot requests request send success! Wait for response!")
    return True


def query_szse_l1_index() -> bool:
    """
    【演示】查询最新深证Level1指数行情信息
    本方法演示如何查询最新深证Level1指数行情信息
    结果将从on_query_szse_l1_index回调中返回
    :return: 查询请求发送是否成功
    """
    if not my_api.query_szse_l1_index(global_session_id, next_request_id(), "399001"):
        error_info = my_api.get_last_error()
        logging.error(f"error_code: {error_info.error_code}, sub_error_code: {error_info.sub_error_code}, error_msg: {error_info.error_msg}")
        return False

    logging.info("Query szse l1 index requests request send success! Wait for response!")
    return True


def query_szse_l1_snapshot() -> bool:
    """
    【演示】查询最新深证Level1快照行情信息
    本方法演示如何查询最新深证Level1快照行情信息
    结果将从on_query_szse_l1_snapshot回调中返回
    :return: 查询请求发送是否成功
    """
    if not my_api.query_szse_l1_snapshot(global_session_id, next_request_id(), "000001"):
        error_info = my_api.get_last_error()
        logging.error(f"error_code: {error_info.error_code}, sub_error_code: {error_info.sub_error_code}, error_msg: {error_info.error_msg}")
        return False

    logging.info("Query szse l1 snapshot requests request send success! Wait for response!")
    return True


def query_szse_l1_snapshot_bond() -> bool:
    """
    【演示】查询最新深证Level1债券快照行情信息
    本方法演示如何查询最新深证Level1债券快照行情信息
    结果将从on_query_szse_l1_snapshot_bond回调中返回
    :return: 查询请求发送是否成功
    """
    if not my_api.query_szse_l1_snapshot_bond(global_session_id, next_request_id(), "000072"):
        error_info = my_api.get_last_error()
        logging.error(f"error_code: {error_info.error_code}, sub_error_code: {error_info.sub_error_code}, error_msg: {error_info.error_msg}")
        return False

    logging.info("Query szse l1 snapshot bond requests request send success! Wait for response!")
    return True


def main():
    """主函数"""
    # 获取API版本号
    logging.info(f"API version: {my_api.get_api_version()}")

    # 初始化
    if not initialize():
        return -1

    # 登录
    if not login():
        return -1

    # 获取交易日
    logging.info(f"Trading day: {my_api.get_trading_day()}")

    # 订阅交易回报
    if not subscribe_report():
        return -1

    # 启动行情
    if not start_quote():
        return -1

    # 查询资金信息
    if not query_assets():
        return -1

    # 查询持仓信息
    if not query_positions():
        return -1

    # 查询订单信息
    if not query_orders():
        return -1

    # 分页查询订单
    if not query_orders_by_page():
        return -1

    # 查询成交信息
    if not query_trades():
        return -1

    # 查询静态行情信息
    if not query_static_quote_info():
        return -1

    # 查询上证Level1指数
    query_sse_l1_index()

    # 查询上证Level1快照
    query_sse_l1_snapshot()

    # 查询深证Level1指数
    query_szse_l1_index()

    # 查询深证Level1快照
    query_szse_l1_snapshot()

    # 查询深证Level1债券快照
    query_szse_l1_snapshot_bond()

    # 插入订单
    xid = insert_order()
    if xid == 0:
        return -1

    # 等待500微秒
    time.sleep(0.0005)

    # 撤单
    cancel_xid = cancel_order(xid)
    if cancel_xid == 0:
        return -1

    # 保持程序运行
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        pass

    # 登出
    my_api.logout(global_session_id)
    return 0


if __name__ == '__main__':
    sys.exit(main())
