15天搭建ETF量化交易系统Day11—miniQMT自动交易真香!


ee5402c2bf88e6235a1d491573f91a43.png

搭建过程

d566c65b298f2434b9af69829c6e3eab.png

每个交易者都应该形成一套自己的交易系统。

 
 

很多交易者也清楚知道,搭建自己交易系统的重要性。现实中,从0到1往往是最难跨越的一步。

授人鱼不如授人以渔,为了帮助大家跨出搭建量化系统的第一步,我们决定推出这个主题系列。

这个系列中,我们用Python从0开始一步步搭建出一套ETF量化交易系统(选择ETF标的是因为对于普通交易者来说,ETF相对于选强势股难度要小,而且没有退市风险)。大家可以跟随着我们的实现路径来一起学习,从过程中掌握方法。

掌握了方法之后,你可以换成期货系统、比特币系统、美股系统,然后在实战中不断去完善自己的系统了。

搭建一套ETF量化交易系统涉及多个模块和组件的协同工作,包括数据源模块、量化策略模块、可视化模块、数据库模块、回测评估模块、自动交易模块等等。

DAY1链接如下:15天搭建ETF量化交易系统Day1—数据源模块
DAY2链接如下:15天搭建ETF量化交易系统Day2—图形显示模块
DAY3链接如下:15天搭建ETF量化交易系统Day3—上手经典回测框架
DAY4链接如下:15天搭建ETF量化交易系统Day4—玩转海龟交易策略
DAY5链接如下:15天搭建ETF量化交易系统Day5—打造实盘量化机器人
DAY6链接如下:15天搭建ETF量化交易系统Day6—打通同花顺自动交易
DAY7链接如下:15天搭建ETF量化交易系统Day7—全自动化交易系统
DAY8链接如下:15天搭建ETF量化交易系统Day8—强化自动交易模块
DAY9链接如下:15天搭建ETF量化交易系统Day9—玩大A必学网格策略

DAY10链接如下:15天搭建ETF量化交易系统Day10—借用网格思想做仓位管理

之前DAY6我们已经打通自动交易环节,采用的方案是使用easytrader库搭建本地自动交易环境,然后用程序自动操作同花顺交易客户端下单。

不过这个属于曲线救国的方案,长期运行并不稳定。在大家的强烈推荐下,我们决定使用正规的量化交易平台作为下单的最后环节——QMT!


5531ed0d16813a716ba1873bb4ebb22b.png

接口介绍

561437ed6be5e5082243f32cfd29b526.png

 
 
QMT(Quantitative Market Trading)是迅投公司开发的量化交易软件,专供券商采购,现在个人投资者也可申请使用。

MiniQMT 是 QMT 的简化版,执行完安装过程这两个就都有了。

MiniQMT的好处是我们可以用自己的量化系统框架,直接向券商发送下单信息。

MiniQMT 提供了一个 XtQuant 的 Python 库,可以 import 它并调用它的方法下单。

XtQuant 目前不能通过 pip 安装,可以下载后放在Python第三方库目录下。

MiniQMT 的下单信息流向如下。

09dd7dfeb15c40574fce09cd61c77ba4.png

在Python实盘代码中import xtquant,通过xtquant库提供的方法下单(提前打开miniQMT),miniQMT 的桌面应用接收到xtquant库发出的下单请求,miniQMT 将下单信息发送给券商的交易服务器。

4c6a030b33e5f210a60c771b54672f56.png

9542ef5eab9360a40bee89561e309da2.png

功能实现

d71a047f5af72bc113dea766379b16c2.png

接下来,我们用miniQMT接口来代替之前easytrader实现的接口,并且把交易的功能封装成一个类,以供系统整体的调用。

类名为QmtTrader,其中包括了“连接客户端”、“获取资金状况”、“获取当前仓位”、“查询当日成交”、“买入”、“卖出”等方法。

class QmtTrader:


    def __init__(self,path= r'C:\国金QMT交易端\userdata_mini',
                account='xxxxxx', account_type='STOCK',
                is_slippage=True, slippage=0.01) -> None:
        '''
        简化版的qmt_trder方便大家做策略的开发类的继承
        '''
        self.xt_trader=''
        self.acc=''
        self.path=path
        self.session_id=int(self.random_session_id())
        self.account=account
        self.account_type=account_type
        if is_slippage==True:
            self.slippage=slippage
        else:
            self.slippage=0
        
    def random_session_id(self):
        '''
        随机id
        '''
        session_id=''
        for i in range(0,9):
            session_id+=str(random.randint(1,9))
        return session_id


    def connect(self):
        '''
        连接
        path qmt userdata_min是路径
        session_id 账户的标志,随便
        account账户,
        account_type账户内类型
        '''
        print('开始链接QMT...')
        # path为mini qmt客户端安装目录下userdata_mini路径
        path = self.path
        # session_id为会话编号,策略使用方对于不同的Python策略需要使用不同的会话编号
        session_id = self.session_id
        xt_trader = XtQuantTrader(path, session_id)
        # 创建资金账号为1000000365的证券账号对象
        account=self.account
        account_type=self.account_type
        acc = StockAccount(account_id=account,    account_type=account_type)


        # 启动交易线程
        xt_trader.start()
        # 建立交易连接,返回0表示连接成功
        connect_result = xt_trader.connect()
        if connect_result==0:
            self.xt_trader=xt_trader
            self.acc=acc
            print('QMT连接成功!')
        else:
            print('QMT连接失败!')


    def get_position(self):
        '''
        查询账户所有的持仓
        '''
        positions = self.xt_trader.query_stock_positions(self.acc)
        print("持仓数量:", len(positions))
        data=pd.DataFrame()
        if len(positions) != 0:
            for i in range(len(positions)):
                df=pd.DataFrame()
                df['账号类型']=[positions[i].account_type]
                df['资金账号']=[positions[i].account_id]
                df['证券代码']=[positions[i].stock_code]
                df['证券代码']=df['证券代码'].apply(lambda x:str(x)[:6])
                df['持仓数量']=[positions[i].volume]
                df['可用数量']=[positions[i].can_use_volume]
                df['平均建仓成本']=[positions[i].open_price]
                df['市值']=[positions[i].market_value]
                data=pd.concat([data,df],ignore_index=True)
            return data
        else:
            print('没有持股')
            df=pd.DataFrame()
            df['账号类型']=[None]
            df['资金账号']=[None]
            df['证券代码']=[None]
            df['持仓数量']=[None]
            df['可用数量']=[None]
            df['平均建仓成本']=[None]
            df['市值']=[None]
            return df


    def get_balance(self):
        '''
        返回当前证券账号的资产数据
        '''
        asset = self.xt_trader.query_stock_asset(account=self.acc)
        data_dict={}
        if asset:
            data_dict['账号类型']=asset.account_type
            data_dict['资金账户']=asset.account_id
            data_dict['可用金额']=asset.cash
            data_dict['冻结金额']=asset.frozen_cash
            data_dict['持仓市值']=asset.market_value
            data_dict['总资产']=asset.total_asset
            return data_dict
        else:
            print('获取失败资金')
            data_dict['账号类型']=[None]
            data_dict['资金账户']=[None]
            data_dict['可用金额']=[None]
            data_dict['冻结金额']=[None]
            data_dict['持仓市值']=[None]
            data_dict['总资产']=[None]
            return  data_dict


    def today_trades(self):
        '''
        当日成交
        '''
        trades = self.xt_trader.query_stock_trades(self.acc)
        print("成交数量:", len(trades))
        data=pd.DataFrame()
        if len(trades) != 0:
            for i in range(len(trades)):
                df=pd.DataFrame()
                df['账号类型']=[trades[i].account_type]
                df['资金账号']=[trades[i].account_id]
                df['证券代码']=[trades[i].stock_code]
                df['证券代码']=df['证券代码'].apply(lambda x:str(x)[:6])
                df['委托类型']=[trades[i].order_type]
                df['成交编号']=[trades[i].traded_id]
                df['成交时间']=[trades[i].traded_time]
                df['成交均价']=[trades[i].traded_price]
                df['成交数量']=[trades[i].traded_volume]
                df['成交金额']=[trades[i].traded_amount]
                df['订单编号']=[trades[i].order_id]
                df['柜台合同编号']=[trades[i].order_sysid]
                df['策略名称']=[trades[i].strategy_name]
                df['委托备注']=[trades[i].order_remark]
                data=pd.concat([data,df],ignore_index=True)
            data['成交时间']=pd.to_datetime(data['成交时间'],unit='s')
            return data


    def today_entrusts(self):
        '''
        当日委托
        :param account: 证券账号
        :param cancelable_only: 仅查询可撤委托
        :return: 返回当日所有委托的委托对象组成的list
        '''
        orders = self.xt_trader.query_stock_orders(self.acc)
        print("委托数量", len(orders))
        data=pd.DataFrame()
        if len(orders) != 0:
            for i in range(len(orders)):
                df=pd.DataFrame()
                df['账号类型']=[orders[i].account_type]
                df['资金账号']=[orders[i].account_id]
                df['证券代码']=[orders[i].stock_code]
                df['证券代码']=df['证券代码'].apply(lambda x:str(x)[:6])
                df['订单编号']=[orders[i].order_id]
                df['柜台合同编号']=[orders[i].order_sysid]
                df['报单时间']=[orders[i].order_time]
                df['委托类型']=[orders[i].order_type]
                df['委托数量']=[orders[i].order_volume]
                df['报价类型']=[orders[i].price_type]
                df['委托价格']=[orders[i].price]
                df['成交数量']=[orders[i].traded_volume]
                df['成交均价']=[orders[i].traded_price]
                df['委托状态']=[orders[i].order_status]
                df['委托状态描述']=[orders[i].status_msg]
                df['策略名称']=[orders[i].strategy_name]
                df['委托备注']=[orders[i].order_remark]
                data=pd.concat([data,df],ignore_index=True)
            data['报单时间']=pd.to_datetime(data['报单时间'],unit='s')
            return data
        else:
            print('目前没有委托')
            return data


    def check_stock_is_av_buy(self, stock='600031', price=17.70, amount=10, hold_limit=100000):
        '''
        检查是否可以买入
        '''
        hold_stock=self.get_position()
        try:
            del hold_stock['Unnamed: 0']
        except:
            pass
        account=self.get_balance()
        try:
            del account['Unnamed: 0']
        except:
            pass
        #买入是价值
        value=price*amount
        cash=account['可用金额']
        frozen_cash=account['冻结金额']
        market_value=account['持仓市值']
        total_asset=account['总资产']
        if cash>=value:
            print('允许买入{} 可用现金{}大于买入金额{} 价格{} 数量{}'.format(stock,cash,value,price,amount))
            return True
        else:
            print('不允许买入{} 可用现金{}小于买入金额{} 价格{} 数量{}'.format(stock,cash,value,price,amount))
            return False


    def check_stock_is_av_sell(self, stock='600031',  amount=10):
        '''
        检查是否可以卖出
        '''
        hold_data=self.get_position()
        try:
            del hold_data['Unnamed: 0']
        except:
            pass
        account=self.get_balance()
        try:
            del account['Unnamed: 0']
        except:
            pass


        cash=account['可用金额']
        frozen_cash=account['冻结金额']
        market_value=account['持仓市值']
        total_asset=account['总资产']
        stock_list=hold_data['证券代码'].tolist()


        if stock in stock_list:
            hold_num=hold_data[hold_data['证券代码']==stock]['可用余额']
            if hold_num>=amount:
                print('允许卖出:{} 持股{} 卖出{}'.format(stock,hold_num,amount))
                return True
            else:
                print('不允许卖出持股不足:{} 持股{} 卖出{}'.format(stock,hold_num,amount))
                return False
        else:
            print('不允许卖出没有持股:{} 持股{} 卖出{}'.format(stock,0,amount))
            return False
    def make_buy(self, security='600031.SH', amount=100, price=20, strategy_name='', order_remark=''):
        '''
        单独独立股票买入函数
        '''
        order_type=xtconstant.STOCK_BUY


        if price == 0: 
            price_type=xtconstant.LATEST_PRICE
        else:
            price_type=xtconstant.FIX_PRICE


        order_volume=amount
        # 使用指定价下单,接口返回订单编号,后续可以用于撤单操作以及查询委托状态
        if order_volume>0:
            fix_result_order_id = self.xt_trader.order_stock(account=self.acc,stock_code=security, order_type=order_type,
                                                order_volume=order_volume, price_type=price_type,
                                                price=price, strategy_name=strategy_name, order_remark=order_remark)


            print('交易类型{} 代码{} 价格{} 数量{} 订单编号{}'.format(order_type, security, price,order_volume,fix_result_order_id))
            return fix_result_order_id
        else:
            print('买入 标的{} 价格{} 委托数量{}小于0有问题'.format(security, price, order_volume))
    
    def make_sell(self,security='600031.SH', amount=100,  price=20, strategy_name='', order_remark=''):
        '''
        单独独立股票卖出函数
        '''
        order_type=xtconstant.STOCK_SELL


        if price == 0: 
            price_type=xtconstant.LATEST_PRICE
        else:
            price_type=xtconstant.FIX_PRICE


        order_volume=amount
        # 使用指定价下单,接口返回订单编号,后续可以用于撤单操作以及查询委托状态
        if order_volume>0:
            fix_result_order_id = self.xt_trader.order_stock(account=self.acc,stock_code=security, order_type=order_type,
                                                                order_volume=order_volume, price_type=price_type,
                                                                price=price, strategy_name=strategy_name, order_remark=order_remark)
            print('交易类型{} 代码{} 价格{} 数量{} 订单编号{}'.format(order_type,security,price,order_volume,fix_result_order_id))
            return fix_result_order_id
        else:
            print('卖出 标的{} 价格{} 委托数量{}小于0有问题'.format(stock_code,price,order_volume))

8bd21e304762000ec22e8e358f28ea73.png

替换接口

fcb0a5d36d5b7292d18cef27a5a8eee0.png

接下来,我们在”Day7—全自动化交易系统“基础上替换新的交易接口。

比如“量化机器人”监测到有ETF符合买入条件时,查询交易账户是否有足够的资金能买入。当账户余额充足时,则立即以当前价格买入。

if self.qmt.check_stock_is_av_buy(code, df_index_data['close'][-1], 1000, 5000) == True:
  self.qmt.make_buy(code, df_index_data['close'][-1], 1000)

比如“量化机器人”监测到有ETF符合卖出条件时,查询交易账户是否有仓位要卖出。当账户有持有的仓位时,则立即以当前价格卖出。

if self.qmt.check_stock_is_av_sell(code, 100) == True:
  self.qmt.make_sell(code, df_index_data['close'][-1], 2000)

5dc1de3ffe90141d456fe40416f5e610.png

总结

877905b810173bd5a008563381037bb3.png

实现了自动下单这个环节之后,接下去我们可以安心地研究策略,打造真正意义上闭环的量化交易系统,全自动化交易。

如何获取QMT安装包和开户可以看这篇介绍:量化交易自动下单方案—对接QMT已出炉

说明

此系列为连载专栏,完整代码会上传知识星球《玩转股票量化交易》!作为会员们的学习资料。

想要加入知识星球《玩转股票量化交易》的小伙伴记得先微信call我获取福利!

非星球会员需要的话,需要单独联系我购买!

知识星球介绍点击:知识星球《玩转股票量化交易》精华内容概览

cc0de52e2a3c01e0c79108f1b6b514db.jpeg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值