聚宽将与一创分手,QMT成最大赢家 | QMT如何与聚宽平台联调呢

| 事件回顾

9月底,一创和聚宽各自在平台上公布一创聚宽实盘将在12月29日关闭,届时无法再提供实盘交易服务。

事件一出,一片哗然。这似乎是在意料之外,情理之中。这几年来,聚宽陆陆续续关停的东西已经很多,比如策略交易,比如归因分析(后续在用户的坚持下转为VIP功能),等等。在今年六七月,由于A股市场的低迷,国家对量化的打击,监管变严,似乎成为了压倒聚宽的最后一根稻草。毕竟在一创聚宽宣布关停之前,才刚准备说要上安全模块呢。

在后续三个月里,小木屋也不断寻找更好的量化平台,心里也期待着聚宽能够找到新的合作方(虽然好像成为一种奢望)。最终敲定了QMT的方案,至于原因嘛,哈哈,就是因为国信的QMT(国信iquant)可以记住密码和自动登录,方便与聚宽进行交互,无需每天在服务器上手动输入密码登录。而国金开放了miniQMT,方便与本地交互,特别是采用AI模型。miniQMT可以使用本地环境,不需要再安装环境,对程序员来说更加友好。

在使用QMT的过程中,当然也是血与泪啊。相对聚宽平台而言,QMT的接口更加难用。举个栗子, run_weekly(weekly_adjustment, 1, ‘9:30’)用于指定函数weekly_adjustment每周一早上9点30分运行一次且仅此一次,run_daily(prepare_stock_list, ‘9:05’)指定函数prepare_stock_list在每天的9点5分运行一次。但是QMT中只有run_time定时函数。run_time(“myHandlebar”,“3nSecond”,“2019-10-14 13:20:00”,“SH”) 该函数的含义为2019-10-14 13:20:00开始每3秒运行一次myHandlebar函数。在这情况下无法精准指定函数在什么时间运行,运行几次,必须通过时间判断和标志位flag判断,从而实现聚宽run_daily和run_weekly的功能。此外,QMT存在行情数据不准确,回测时间过久等问题,因此QMT的定位更多是作为一个下单工具使用。

| QMT与其他下单软件之争

吐槽归吐槽,但是QMT确实是拯救聚宽实盘的一个绝佳方案了。什么,你跟我说Ptrade?大部分Ptrade可是不支持外部连接数据库的。easytrader?这种不是跟证券直连的先不说延时速度怎样,光是时不时就断线和服务崩溃已经够小木屋喝一壶了,唯一的好处就是自由度确实非常高。

如果你让我首选,我会选miniQMT。毕竟miniQMT解决了下单延时问题和服务崩溃问题,自由度非常高,适合像小木屋这样的程序员,搭配supervisor-win监控程序运行,应该是一个不错的选择。但是miniQMT并不是所有证券公司都会开放的,毕竟涉及合规问题,而且这个方案小木屋还没尝试,以后搞定了再分享。

接下来,小木屋先讲解QMT的方案,从聚宽如何输出订单信息给数据库。在本文中,小木屋采用的是mysql数据库,且是服务器自带的数据库(为了省钱,如果读者们考虑实时性和稳定性也可以购买云数据库)。

读者们在自己的聚宽代码里修改,主要是这两大函数:


# 创建一个基类,用于声明数据模型
Base = declarative_base()

class JoinQuantTable(Base):
    __tablename__ = 'joinquant_stock'  # 设置数据库表名称

    pk = Column(String(36), primary_key=True, default=str(uuid.uuid1()))  # 唯一识别码,可以理解为订单号,区分不同的订单
    code = Column(String)  # 证券代码
    tradetime = Column(DateTime, default=datetime.datetime.now())  # 交易时间
    order_values = Column(Integer) # 下单数量,可以改名为amount
    price = Column(Integer)  # 下单价格
    ordertype = Column(String) # 下单方向,买 或 卖
    if_deal = Column(Boolean, default=False) # 是否已经成交
    insertdate = Column(DateTime, default=datetime.datetime.now()) # 订单信息插入数据库的时间

在上述这段代码中,要特别注意的是tablename数据库表名,改成自己的。剩下的order_values、price、ordertype 是否需要这三个特征就看自己了。小木屋只需要持仓信息,因此只需要提前(9点15分,避免9点30分的高峰期)把持仓信息提前发给数据库,再交给迅投QMT处理,因此order_values、price、ordertype对我不重要,code是对的即可。


#5.2下单指令入库函数
def push_order_command(order_dict_list):
    def format_code(code):
        code = code.replace('.XSHE','.SZ')
        code = code.replace('.XSHG','.SH')
        return code

    try:    
        # Define your database connection parameters
        db_user = 'xxxxxx' # 修改为你的数据库用户名
        db_password = 'xxxxx' # 修改为你的数据库密码
        db_host = 'xxxx.x.xxx.xx' # 修改为你的数据库ip
        db_name = 'xxxxxxx' # 修改为你的数据库名称
        # Create an SQLAlchemy engine
        engine = create_engine(f'mysql://{db_user}:{db_password}@{db_host}/{db_name}')
        # 创建一个基类,用于声明数据模型
      # Create a session
        Session = sessionmaker(bind=engine)
        session = Session()

        for order_dict in order_dict_list:
            pk = order_dict['pk'] # 唯一识别码,可以理解为订单号,区分不同的订单
            code = format_code(order_dict['code']) # 证券代码
            tradetime = order_dict['tradetime'] # 交易时间
            order_values = order_dict['order_values'] # 下单数量,可以改名为amount
            price = order_dict['price'] # 下单价格
            ordertype = order_dict['ordertype'] # 下单方向,买 或 卖
            if_deal = order_dict['if_deal'] # 是否已经成交
            insertdate = order_dict['insertdate'] # 订单信息插入数据库的时间

            # Create a new record
            new_record = JoinQuantTable(
                pk=pk,
                code=code,
                tradetime=tradetime,
                order_values=order_values,
                price=price,
                ordertype=ordertype,
                if_deal=if_deal,
                insertdate=insertdate
            )

            # Add the record to the session
            session.add(new_record)

            # Commit the changes to the database
            session.commit()

        # Close the session
        session.close()
    except Exception as e:
        print('数据库出错')
        print(e)

这部分的内容根据上面的自己修改后的信息进行修改。db_user、db_password、db_host和db_name记得需要修改。由于order_dict_list是一个列表,因此把聚宽下单信息(字典)都整合进列表list中再传给数据库,这样可以减少不断打开和关闭数据库导致的延时。一个下单信息应该包含pk到insertdate共8个信息,当然,也可以自己修改。记得聚宽和QMT都要修改即可。

QMT部分的代码,先说一下踩过的坑:

(1)尽量用run_time函数,不要用handlebar,对于开盘就需要交易的朋友很重要。(2)数据库挂掉后,QMT代码也无法继续运行。(3)当购买很多股票时,委托了但无法成交。(4)没有设置标志位,导致买股票时多次委托。

迅投QMT部分的思路如下:

(1)采用run_time函数,确保代码能在9点30分能运行起来,防止handlebar会有延迟。(2)采用run_time是每3秒运行一次,确保留住时间给代码去读取数据库;在读取数据库的时候需要采用异常处理语句try-except,防止数据库挂掉或其他原因导致的QMT代码无法正常运行。(3)小木屋是设定了早上9点29分到下午15点30分运行代码,会一直轮询数据库,确保数据库读取后第一时间让QMT下单。9点30分5秒这一刻会进行下单操作,为了防止开盘波动较大且订单量较小。如果读者的资金量过大,可以考虑买5价和卖5价进行交易,虽然有一定滑点,但滑点不会很大且能确保交易成功;又或者进行拆单操作,相隔一定时间再继续下单。(4)小木屋在中午收盘和晚上收盘前几分钟删除数据库的所有信息,防止订单信息交叉错误。

| 核心代码

def init(ContextInfo):
    global position_flag, delete_flag, order_flag

    ContextInfo.run_time("myHandlebar","3nSecond","2019-10-14 13:20:00","SH")

    position_flag = False
    delete_flag = True
    order_flag = True
    account = "xxxxxxxx"
    ContextInfo.accID = str(account)
    ContextInfo.set_account(ContextInfo.accID)

    print('init')

从数据库获取订单信息和删除订单信息


def get_data(query_str):
    today_date = datetime.today().date()
    today_date = today_date.strftime('%Y-%m-%d')
    host = "xxx.x.xxx.xx"
    port = 3306
    user = "xxxxxx"
    password = "xxxxxxx"
    database = 'xxxxx'
    # 连接 MySQL 数据库
    conn = pymysql.connect(host=host, port=port, user=user,
                                   password=password, database=database,
                                   charset='utf8')
    cursor = conn.cursor()

    # 执行 SQL 查询语句
    cursor.execute(query_str)
    # 获取查询结果
    result = cursor.fetchall()

    # 将查询结果转化为 Pandas dataframe 对象
    res  = pd.DataFrame([result[i] for i in range(len(result))], columns=[i[0] for i in cursor.description])

    res['tradedate'] = res['tradetime'].apply(lambda x:x.strftime('%Y-%m-%d'))
    res = res[res['tradedate'] == today_date]
    target_stock = res['code'].tolist()

    cursor.close()
    conn.close()

    return target_stock

def delete_data():
    query_str = """DELETE FROM joinquant.joinquant_stock_shipan5S"""
    host = "xxx.x.xxx.xx"
    port = 3306
    user = "xxxxxx"
    password = "xxxx"
    database = 'xxxxxx'
    try:
        # 连接 MySQL 数据库
        conn = pymysql.connect(host=host, port=port, user=user,
                                       password=password, database=database,
                                       charset='utf8')
        cursor = conn.cursor()

        # 执行 SQL 查询语句
        cursor.execute(query_str)
        conn.commit()
        cursor.close()
        conn.close()
        print('删除数据库中的所有数据')
    except Exception as e:
       print('错误:{}'.format(e))

    return

核心运行代码,指定交易时间进行下单操作


def myHandlebar(ContextInfo):
    global position_flag, delete_flag, order_flag

    current_time = datetime.now().time()

    # 设置起始和结束时间
    morning_start_time = time(9, 30, 5)
    morning_end_time = time(9, 32)

    morning_delete_database_start = time(11, 29)
    morning_delete_database_end = time(11, 30)

    target_stock = []
    buy_direction = 23
    sell_direction = 24
    SALE3 = 2
    BUY3 = 8
    lastest_price = 5

    day_start_time = time(9, 29)
    day_end_time = time(15, 30)

    if day_start_time <= current_time and current_time <= day_end_time:
        # print('handlebar:{}'.format(datetime.now()))

        query_str = """select * from joinquant.joinquant_stock_shipan5S"""

        try:
            target_stock = get_data(query_str)
        except Exception as e:
            target_stock = []
            print('发生错误,错误:{}'.format(e))

        if len(target_stock) < 1:
            return

        if morning_start_time <= current_time and current_time <= morning_end_time:

            position_flag = True
            delete_flag = True

            if order_flag == False:
                return

            position_info = get_trade_detail_data(ContextInfo.accID, 'stock', 'position')
            postion_code = []
            pistion_volumn = {}
            if len(position_info) > 0:
                for ele in position_info:
                    if ele.m_nVolume > 0:
                        code_num = code_prefix_dix(ele.m_strInstrumentID)
                        postion_code.append(code_num)
                        pistion_volumn[code_num] = ele.m_nVolume

            lastest_postion_code = postion_code.copy()

            acc_info = get_trade_detail_data(ContextInfo.accID, 'stock', 'account')
            avail_balance = acc_info[0].m_dAvailable
            order_num = 5
            order_value = avail_balance / order_num
            order_value = order_value / 1000 # 实盘的时候去掉

            for code_ele in postion_code[::-1]:
                if code_ele not in target_stock:
                    passorder(sell_direction, 1101, ContextInfo.accID, code_ele, BUY3, -1, pistion_volumn[code_ele], '', 1, '', ContextInfo)
                    del lastest_postion_code[postion_code.index(code_ele)]
            print('postion_code:', postion_code)

            acc_info = get_trade_detail_data(ContextInfo.accID, 'stock', 'account')
            avail_balance = acc_info[0].m_dAvailable

            order_num = len(target_stock) - len(lastest_postion_code)

            if order_num != 0:
                order_value = avail_balance / order_num
                order_value = order_value / 1000 # 实盘的时候去掉           

                for code_ele_buy in target_stock:
                    if code_ele_buy not in lastest_postion_code:
                        passorder(buy_direction, 1102, ContextInfo.accID, code_ele_buy, SALE3, -1, 20000, '', 1, '', ContextInfo)
                        lastest_postion_code.append(code_ele_buy)
                        if len(lastest_postion_code) >= len(target_stock):
                            break

            order_flag = False    

        elif morning_delete_database_start < current_time and current_time < morning_delete_database_end:
            order_flag = True
            if delete_flag == True:
                delete_data()
                delete_flag = False

喜欢小木屋分享的内容的话,请记得关注小木屋。完整代码可以关注小木屋后私信领取哦。

如果有需要开通QMT的也可以私信小木屋哦

你可能感兴趣的:(量化,microsoft,python,机器学习,cnn)