QUANTAXIS探索(七)量化策略实现类QAStrategy的回测模式

QAStrategy 包实现了常用的策略流程的封装,包括股票和期货的。支持回测、模拟/仿真、实盘,支持股票、期货。
qactabase.py: 定义用于期货的策略。但感觉该类并未最终的完善类,还处于中间开发状态,但也可以看其主要的实现逻辑。

主要的基类是QAStrategyCTABase,位于QAStrategy.qactabase。
股票回测为QAStrategyStockBase,继承自QAStrategyCTABase,微调了部分逻辑。

本文分析使用qastrategy==0.0.25,先来看QAStrategyCTABase的回测。

回测模式:run_backtest

分为调试、保存账户数据、保存风险分析数据、发送策略评价数据(未开源)
回测模式对单标的有效,多标的会出错。

 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

在调用之前,需要初始化策略。

初始化(__init__)

代码加注释如下:

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函数。

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)

[附:文档API-Position]


Postions 关于你的持仓的一切

当我们开始写策略的时候, 必不可少的, 我们会跟持仓打交道,

  • 当行情变化的时候, 持仓的盈亏的计算(成本的统计视角)
  • 当我们要平仓的时候, 持仓(今仓/昨仓)的判断
  • 我们预估浮盈浮亏的时候, 对于仓位开仓的信号点进行的判断
  • 我们进行对冲/对锁操作时, 对于双向开仓的需求

等等, 因此, QAPositions 就是面向这个需求来解决问题的, 我们把对于一个品种的持仓成为一个positions, 在一个position中你可以有开多和开空两种独立的状态

如何获取到持仓?

你可以用

```python
positions = self.get_position(code)
```

在单标的基类中, 你可以直接调用 positions,  如果是多标的基类  会需要使用 self.acc.get_position(code)

position 在回测/模拟中都是一样的  属于 QAPostion类

如何使用QAPosition?

如果你需要查询当前的仓位:  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  # 基于开仓价计算的多头开仓价空头成本

```

Position 原理是什么

本质是QAPosition 属于 ```QUANTAXIS.QAMarket.QAPosition```

[附:文档API]-QAStrategyCTA


1. QAStrategyCTABase

QAStrategy是被设计成(起码我是这么想的)成 “”“支持股票期货/ 支持一个函数切换回测/模拟/SIM/实盘”""的基类

QAStrategyCTABase 是面向常见cta场景

一般是期货场景/ 兼容股票

-  单标的
-  事件驱动
-  不进行选股/ 大量的择时操作

1.1 如何使用QAStrategyCTABase:

  1. docker用户需要先拉起期货/股票的docker, 具体的区别见之前的教程:
    http://www.yutiansut.com:3000/topic/5dc5da7dc466af76e9e3bc5d

  2. 非docker用户需要自行部署, 具体部署流程参见之前的教程
    https://github.com/QUANTAXIS/QUANTAXIS/issues/1349

简单来说, 使用QAStrategy 你需要两步

  • 搭建本地的行情推送服务器
    回测环境 由QUANTAXIS 自身项目提供, 需要先手动save数据
    模拟环境/实盘环境 由QAREALTIME_COllECTOR项目提供, 内置在docker
    SIM环境(随机tick/ 真实tick) 由QAREALTIME_COLLECTOR/ QARANDOM_PRICE 提供, 内置在docker

  • 使用QAStrategy 提供行情流推送来的数据处理节点(策略)

1.2 一个简单的示例

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给你

1.3 下单/行情/账户/指标 的常见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上

1.3.1 下单函数 send_order

下单函数被设计的非常简单:

self.send_order('BUY', 'OPEN', price=bar['close'], volume=1)

只需要4个函数你就可以下单了, 当然 这个场景是CTABase的, 在多标的的stockbase中, 你还需要带上你的code 这个在当前不讨论

1.3.2 接受到的行情数据 self.market_data

self.market_data 是一个惰性计算的函数(@property), 因此当你收到这个数据, 你需要先复制一份来使用

这是一个multiindex的 dataframe, 如果你熟悉QUANTAXIS的 QADataStruct, 你会非常熟悉, 因为multiindex可以方便的承载多个时间/多个标的的数据


def on_bar(self, bar):
    market_data = self.market_data

    print(market_data)
1.3.3 账户 self.acc

你需要注意的是, self.acc 在不同的模式下是不一样的

在回测模式: self.acc是一个实例化的 QUANTAXIS.QAAccountPro 类

在模拟模式: self.acc是一个实例化的 qifiaccount 账户

1.3.4 持仓 self.positions

在单标的基类中, 你可以直接调用 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  # 基于开仓价计算的多头开仓价空头成本

1.3.5 指标

让我们回归到上面的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))

你可能感兴趣的:(金融)