搭建过程
每个交易者都应该形成一套自己的交易系统。
很多交易者也清楚知道,搭建自己交易系统的重要性。现实中,从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!
接口介绍
QMT(Quantitative Market Trading)是迅投公司开发的量化交易软件,专供券商采购,现在个人投资者也可申请使用。
MiniQMT 是 QMT 的简化版,执行完安装过程这两个就都有了。
MiniQMT的好处是我们可以用自己的量化系统框架,直接向券商发送下单信息。
MiniQMT 提供了一个 XtQuant 的 Python 库,可以 import 它并调用它的方法下单。
XtQuant 目前不能通过 pip 安装,可以下载后放在Python第三方库目录下。
MiniQMT 的下单信息流向如下。
在Python实盘代码中import xtquant,通过xtquant库提供的方法下单(提前打开miniQMT),miniQMT 的桌面应用接收到xtquant库发出的下单请求,miniQMT 将下单信息发送给券商的交易服务器。
功能实现
接下来,我们用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))
替换接口
接下来,我们在”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)
总结
实现了自动下单这个环节之后,接下去我们可以安心地研究策略,打造真正意义上闭环的量化交易系统,全自动化交易。
如何获取QMT安装包和开户可以看这篇介绍:量化交易自动下单方案—对接QMT已出炉
说明
此系列为连载专栏,完整代码会上传知识星球《玩转股票量化交易》!作为会员们的学习资料。
想要加入知识星球《玩转股票量化交易》的小伙伴记得先微信call我获取福利!
非星球会员需要的话,需要单独联系我购买!
知识星球介绍点击:知识星球《玩转股票量化交易》精华内容概览