聚宽将与一创分手,QMT竟成最大赢家?

聚宽将与一创分手,QMT竟成最大赢家?在今年六七月 由于 A 股市场的低迷 国家对量化的打击 监管变严 似乎成为了压倒聚宽的最后一根稻草

| 事件回顾

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的也可以私信小木屋哦

今天的文章 聚宽将与一创分手,QMT竟成最大赢家?分享到此就结束了,感谢您的阅读。
编程小号
上一篇 2024-12-31 17:30
下一篇 2024-12-31 17:27

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ji-chu/97690.html