QAStrategy 包实现了常用的策略流程的封装,包括股票和期货的。支持回测、模拟/仿真、实盘,支持股票、期货。
qactabase.py: 定义用于期货的策略。但感觉该类并未最终的完善类,还处于中间开发状态,但也可以看其主要的实现逻辑。
主要的基类是QAStrategyCTABase,位于QAStrategy.qactabase。
股票回测为QAStrategyStockBase,继承自QAStrategyCTABase,微调了部分逻辑。
本文分析使用qastrategy==0.0.25,先来看QAStrategyCTABase的回测。
分为调试、保存账户数据、保存风险分析数据、发送策略评价数据(未开源)
回测模式对单标的有效,多标的会出错。
def run_backtest(self):
self.debug()
self.acc.save()
risk = QA_Risk(self.acc)
risk.save()
try:
"""add rank flow if exist
QARank是我们内部用于评价策略ELO的库 此处并不影响正常使用
"""
from QARank import QA_Rank
QA_Rank(self.acc).send()
except:
pass
在调用之前,需要初始化策略。
代码加注释如下:
def __init__(self, code='rb2005', frequence='1min', strategy_id='QA_STRATEGY', risk_check_gap=1, portfolio='default',
start='2020-01-01', end='2020-05-21', init_cash=1000000, send_wx=False,
data_host=eventmq_ip, data_port=eventmq_port, data_user=eventmq_username, data_password=eventmq_password,
trade_host=eventmq_ip, trade_port=eventmq_port, trade_user=eventmq_username, trade_password=eventmq_password,
taskid=None, mongo_ip=mongo_ip, model='py'):
"""
code 可以传入单个标的 也可以传入一组标的(list)
会自动基于code来判断是什么市场
TODO: 支持多个市场同时存在
self.trade_host 交易所在的eventmq的ip [挂ORDER_ROUTER的]
"""
self.username = 'admin' #也可以修改__init__函数实现自定义用户名和密码
self.password = 'admin'
self.trade_host = trade_host # 代码有重复,可删
self.code = code #单只股票/股票list
self.frequence = frequence # eg. 1min, day
self.strategy_id = strategy_id
self.portfolio = portfolio
#模拟交易中用于指定数据服务器信息
self.data_host = data_host
self.data_port = data_port
self.data_user = data_user
self.data_password = data_password
#模拟交易中用于指定交易服务器信息
self.trade_host = trade_host # 与上文重复
self.trade_port = trade_port
self.trade_user = trade_user
self.trade_password = trade_password
#开始日期/结束日期/初始现金/任务id
self.start = start
self.end = end
self.init_cash = init_cash
self.taskid = taskid
self.running_time = '' # 运行时间(更新的时机为_on_1min_bar与on_bar之间),表明当前在处理哪个bar
self.market_preset = QA.QAARP.MARKET_PRESET() #市场基础信息,如期货合约名称、单位、佣金等信息
self._market_data = [] #模拟交易中,用于存储推送的行情数据
self.risk_check_gap = risk_check_gap #用于接入风控程序,每隔指定时间运行risk_check()
self.latest_price = {} # 未使用到
self.isupdate = False # 标记新的推送数据
self.model = model #区分py/rust,数据订阅会不同
self.new_data = {} # 在callback中存储新推送的数据
self._systemvar = {} # 未使用到
self._signal = [] # 未使用到
self.send_wx = send_wx # 用于是否发送订单数据到微信
# 处理单个或多个code
if isinstance(self.code, str):
self.last_order_towards = {self.code: {'BUY': '', 'SELL': ''}}
else:
self.last_order_towards = dict(
zip(self.code, [{'BUY': '', 'SELL': ''} for i in range(len(self.code))]))
self.dt = '' # 模拟交易中用于存储推送数据的时间,从self.new_data中获得数据
# 判断是股票还是期货市场,分单只和多只处理。
if isinstance(self.code, str):
self.market_type = MARKET_TYPE.FUTURE_CN if re.search(
r'[a-zA-z]+', self.code) else MARKET_TYPE.STOCK_CN
else:
self.market_type = MARKET_TYPE.FUTURE_CN if re.search(
r'[a-zA-z]+', self.code[0]) else MARKET_TYPE.STOCK_CN
# 未发现使用场景
self.bar_order = {'BUY_OPEN': 0, 'SELL_OPEN': 0,
'BUY_CLOSE': 0, 'SELL_CLOSE': 0}
# 设置缓存参数
self._num_cached = 120
self._cached_data = []
self.user_init() #用户可重写该函数实现策略的初始工作。
初始化完成后,就可以调用run_backtest,这个函数会调用debug函数。
实现了连接数据库、载入历史交易数据、获取交易数据、迭代数据进行回测(顺序:_on_1min_bar -> on_bar),所以策略运行于分钟还是日线级别,取决于覆写哪个函数。
如果是分钟线,到了第二天,会在当天结束后、第二天开始前执行on_dailyclose -> on_dailyopen 。
加注释到代码如下:
def debug(self):
self.running_mode = 'backtest'
self.database = pymongo.MongoClient(mongo_ip).QUANTAXIS
user = QA_User(username=self.username, password=self.password)
port = user.new_portfolio(self.portfolio)
self.acc = port.new_accountpro(
account_cookie=self.strategy_id, init_cash=self.init_cash, market_type=self.market_type, frequence=self.frequence)
#这里应用多股回测会出问题。
self.positions = self.acc.get_position(self.code)
print(self.acc)
print(self.acc.market_type)
#这里应用多股回测会出问题。
data = QA.QA_quotation(self.code.upper(), self.start, self.end, source=QA.DATASOURCE.MONGO,
frequence=self.frequence, market=self.market_type, output=QA.OUTPUT_FORMAT.DATASTRUCT)
data.data.apply(self.x1, axis=1) #利用pandas的apply函数推动循环
对数据行进行处理。
【!】对于多股回测需要注意,因为每一行就会调用该函数一次。
由于使用pandas 的apply推动循环,所以item为dataframe的数据行。
def x1(self, item):
self.latest_price[item.name[1]] = item['close']
if str(item.name[0])[0:10] != str(self.running_time)[0:10]:
self.on_dailyclose()
self.on_dailyopen()
if self.market_type == QA.MARKET_TYPE.STOCK_CN:
print('backtest: Settle!')
self.acc.settle()
self._on_1min_bar()
self._market_data.append(copy.deepcopy(item))
self.running_time = str(item.name[0])
self.on_bar(item)
当我们开始写策略的时候, 必不可少的, 我们会跟持仓打交道,
等等, 因此, QAPositions 就是面向这个需求来解决问题的, 我们把对于一个品种的持仓成为一个positions, 在一个position中你可以有开多和开空两种独立的状态
你可以用
```python
positions = self.get_position(code)
```
在单标的基类中, 你可以直接调用 positions, 如果是多标的基类 会需要使用 self.acc.get_position(code)
position 在回测/模拟中都是一样的 属于 QAPostion类
如果你需要查询当前的仓位: positions.cur_vol / positions.hold_detail
如果你需要查询仓位的全部信息: self.positons.static_message
如果你需要知道详细的基于当前价格的动态信息: self.position.realtime_message
以下列出了一些你可以直接调用的信息, 这些信息在行情更新/下单成交的时候都已经自动计算好了
```python
positions.volume_long #当前持的多单
positions.volume_short #当前持仓的空单数量
positions.volume_long_today #今日多单数量
positions.volume_long_his #今日多单数量
positions.volume_short_today #今日空单数量
positions.volume_short_his #今日空单数量
positions.position_price_long # 基于结算价计算的多头成本价
positions.position_cost_long # 基于结算价计算的多头总成本(总市值)
positions.position_price_short # 基于结算价计算的空头开仓均价
positions.position_cost_short # 基于结算价计算的空头成本
positions.open_price_long # 基于开仓价计算的多头开仓价
positions.open_cost_long # 基于开仓价计算的多头开仓价
positions.open_price_short # 基于开仓价计算的多头开仓价空头开仓价
positions.open_cost_short # 基于开仓价计算的多头开仓价空头成本
```
本质是QAPosition 属于 ```QUANTAXIS.QAMarket.QAPosition```
QAStrategy是被设计成(起码我是这么想的)成 “”“支持股票期货/ 支持一个函数切换回测/模拟/SIM/实盘”""的基类
QAStrategyCTABase 是面向常见cta场景
一般是期货场景/ 兼容股票
- 单标的
- 事件驱动
- 不进行选股/ 大量的择时操作
docker用户需要先拉起期货/股票的docker, 具体的区别见之前的教程:
http://www.yutiansut.com:3000/topic/5dc5da7dc466af76e9e3bc5d
非docker用户需要自行部署, 具体部署流程参见之前的教程
https://github.com/QUANTAXIS/QUANTAXIS/issues/1349
简单来说, 使用QAStrategy 你需要两步
搭建本地的行情推送服务器
回测环境 由QUANTAXIS 自身项目提供, 需要先手动save数据
模拟环境/实盘环境 由QAREALTIME_COllECTOR项目提供, 内置在docker
SIM环境(随机tick/ 真实tick) 由QAREALTIME_COLLECTOR/ QARANDOM_PRICE 提供, 内置在docker
使用QAStrategy 提供行情流推送来的数据处理节点(策略)
from QAStrategy import QAStrategyCTABase
import QUANTAXIS as QA
class Strategy(QAStrategyCTABase):
def on_bar(self, bar):
print(bar)
s = Strategy(code='rb2005', frequence='1min', strategy_id= 'xxx1' )
s.debug_sim()
你可以看到 由这几行代码 你就可以实现一个realtime的实时模拟策略 当然 这个策略只干了一个事情, 就是当行情来的时候, 打印行情价格
当然, 如果这是个回测代码, 你需要继续指定下开始和结束时间
from QAStrategy import QAStrategyCTABase
import QUANTAXIS as QA
class Strategy(QAStrategyCTABase):
def on_bar(self, bar):
print(bar)
s = Strategy(code='rb2005', frequence='1min', strategy_id= 'xxx1', start='2019-10-01', end='2019-11-01')
s.run_backtest()
你可以看到 当我们切换模式的时候, 策略主体并没有发生任何改变 如果当你已经理解了这个代码 我们可以逐步的往下介绍新的API给你
为了降低理解难度, 方便你一步一步的理解这个QAStrategy, 我在最开始的时候, 并不会考虑介绍太多的api给你, 也不会讲述底层实现过程, 我希望你可以只知道5个函数你也能一样的写策略
当前策略时间点: self.running_time
下单: self.send_order
接受到的数据: self.market_data
账户: ```self.acc``
在这里, 账户的内容是非常多的, 此处并不细讲, 在后面逐步展开
self.acc.trade
self.acc.history_table
持仓: self.poistions
指标: 你可以直接使用QA_Indicator系列, 加载到self.market_data上
下单函数被设计的非常简单:
self.send_order('BUY', 'OPEN', price=bar['close'], volume=1)
只需要4个函数你就可以下单了, 当然 这个场景是CTABase的, 在多标的的stockbase中, 你还需要带上你的code 这个在当前不讨论
self.market_data 是一个惰性计算的函数(@property), 因此当你收到这个数据, 你需要先复制一份来使用
这是一个multiindex的 dataframe, 如果你熟悉QUANTAXIS的 QADataStruct, 你会非常熟悉, 因为multiindex可以方便的承载多个时间/多个标的的数据
def on_bar(self, bar):
market_data = self.market_data
print(market_data)
你需要注意的是, self.acc 在不同的模式下是不一样的
在回测模式: self.acc是一个实例化的 QUANTAXIS.QAAccountPro 类
在模拟模式: self.acc是一个实例化的 qifiaccount 账户
在单标的基类中, 你可以直接调用 self.positions, 如果是多标的基类 会需要使用 self.acc.get_position(code)
position 在回测/模拟中都是一样的 属于 QAPostion类
如果你需要查询当前的仓位: self.positions.cur_vol / self.positions.hold_detail
如果你需要查询仓位的全部信息: self.positons.static_message
如果你需要知道详细的基于当前价格的动态信息: self.position.realtime_message
以下列出了一些你可以直接调用的信息, 这些信息在行情更新/下单成交的时候都已经自动计算好了
self.positions.volume_long #当前持的多单
self.positions.volume_short #当前持仓的空单数量
self.positions.volume_long_today #今日多单数量
self.positions.volume_long_his #今日多单数量
self.positions.volume_short_today #今日空单数量
self.positions.volume_short_his #今日空单数量
self.positions.position_price_long # 基于结算价计算的多头成本价
self.positions.position_cost_long # 基于结算价计算的多头总成本(总市值)
self.positions.position_price_short # 基于结算价计算的空头开仓均价
self.positions.position_cost_short # 基于结算价计算的空头成本
self.positions.open_price_long # 基于开仓价计算的多头开仓价
self.positions.open_cost_long # 基于开仓价计算的多头开仓价
self.positions.self.open_price_short # 基于开仓价计算的多头开仓价空头开仓价
self.positions.open_cost_short # 基于开仓价计算的多头开仓价空头成本
让我们回归到上面的market_data, 我们来计算一个指标
import QUANTAXIS as QA
def on_bar(self, bar):
market_data = self.market_data
print(market_data)
print(QA.QA_Indicator_MACD(market_data, 12, 26, 9))