基于RiceQuant的期货多因子策略实现(一)

发布时间 2023-08-10 15:16:08作者: LazyTiming

前言

基于多因子模型理论,介绍其在国内期货的应用,并使用米宽(RiceQuant)的RQFactor进行实现。

理论基础

CAMP: 资产的预期超额收益由 市场组合的预期超额收益 与 资产对市场风险的暴露决定

=========================================》

APT: 在 CAPM 的基础上进行了延伸,将决定资产收益率的风险来源扩充至多个因子

更加合理的表达式,考虑存在一个定价误差

=========================================》

MFM:等价于APT,换个表现形式

算子与因子

算子

定义: 面向因子 可复用的 逻辑操作

进阶: 根据算子的操作纬度 可以将算子分为3大类

  1. 时序算子
    每个标的的因子逻辑操作,只涉及该标的因子数据。
    根据时序算子的复杂度,可以继续划分:
  • 简单算子
    一种算子计算的结果只与输入因子的当期值有关,这种算子输出的因子值长度与输入因子值相同
    如:+,-,LOG
  • 滑动窗口算子
    根据输入因子的一个时间序列进行计算
    如:MA,SUM,TS_RANK

特别的,如REF算子,可以看做是简单算子(因为python提供了便捷的shift操作,让用户不必调用滑动窗口就能获取到值),
也可以作为滑动窗口算子(REF涉及到了时间序列操作)

  1. 截面算子
    每个标的的因子逻辑操作,涉及该标的因子数据以及标的池中其他标的的因子数据
    如:RANK

因子

定义: 描述 某个标的某个周期的特征。

进阶: 根据因子的依赖因子构成 可以将算子分为2大类

  1. 基础因子
    基础因子的生成,不依赖其他因子
  • open,high,low,close,volume等 可直接获取的行情数据
  • 仓单,基本面等 需要进行数据处理 但不依赖其他因子数据
  1. 复合因子
    复合因子本质是 算子应用于 单个或多个因子(包含 基础因子以及复合因子)所派生的因子
    因此,基于他们所依赖的算子,根据算子的复杂度,可以进一步划分
    • 简单复合因子
      =》因子 + 简单算子
      =》如: LOG(OPEN-CLOSE)-->依赖因子:OPEN,CLOSE 依赖算子: LOG,-

    • 滑动窗口因子
      =》因子 + 滑动窗口算子 + (简单算子)
      =》如: TS_RANK(CLOSE- MA(CLOSE,20)) -->依赖因子:CLOSE 依赖算子: TS_RANK,MA,-

    • 截面复合因子
      =》因子 + 截面算子 +(滑动窗口算子 + 简单算子)
      =》如: RANK(TS_RANK(CLOSE- MA(CLOSE,20))) -->依赖因子:CLOSE 依赖算子: RANK,MA, -

米框提供的基类

  • 因子
    自定义因子 ==》 UserDefinedLeafFactor (原型为 UserDefiendLeafFactor(name, func), func返回一个序列 作为因子返回值)

  • 算子
    简单算子 ==》CombinedFactor(func, factors): 定义在rqfactor.extension中;其接受的func原型为func(series);
    滑动窗口算子 ==》RollingWindowFactor(func, window, factor): 定义在rqfactor.extension中;func函数原型为def func(series, window);
    ==》CombinedRollingWindowFactor(func, window, *factors): 定义在rqfactor.extension中,接受多个因子作为输入,func函数原型为def func(window, *series).
    横截面算子 ==》ombinedCrossSectionalFactor(func, factors): 定义在 rqfactor.extension 中,其中 func 的原型为 func(dfs)

因子编写流程

根据上述理论以及米框对于因子的抽象,编写一个因子的时候,可以安装一下流程规范进行:
1> 解析因子逻辑,确定因子类型,调用合适的最外层算子
2> 按需求实现需要自定义的算子
3> 确定 基础因子,使用UserDefinedLeafFactor类自定义系统中不提供的因子。
4> 根据基础因子 实现 简单复合因子。
5> 整合算子和因子,完成编写

举例

以如下因子为例,用5min交易数据来构建量价相关性因子。对于每个品种,t日的量价相关性因子采用以下方式构建:

我们取j=21 得到日级别因子corr_vp_5m_d21

方案1
该因子需要计算21日的相关性之和,并且无截面操作
确定该因子为滑动窗口因子(SUM(factor_corr,21))

实现代码如下

def get_m5_vp(df):
    # data = pd.Series([3, -1, 4, 5, -2])
    l1_norm = np.linalg.norm(df['volume'], ord=1)
    a = df['volume']/l1_norm
    b = df['close']
    # 计算相关性系数
    correlation_matrix = np.corrcoef(a, b)
    # 获取 a 和 b 之间的相关性系数
    correlation_coefficient = correlation_matrix[0, 1]
    return correlation_coefficient

def get_day_corr_vp(order_book_ids, start_date, end_date):
    data = rqdatac.get_price(order_book_ids, start_date, end_date, fields=['volume','close'], frequency='5m', adjust_type='none',expect_df=True)
    # 考虑夜盘归属
    data['date'] = pd.to_datetime( data.index.get_level_values(1).date)
    data['night'] = (data.index.get_level_values(1).hour>20) | (data.index.get_level_values(1).hour<8)
    today = list(set(data['date']))
    today.sort()
    next_day = today[1:].copy()
    next_day.append(None)
    dict_next_trade_day = dict(zip(today,next_day))
    data['date'] =  data.apply(lambda x: dict_next_trade_day[x['date']] if x['night'] else x['date'], axis=1)
    df = data.groupby(['order_book_id','date']).apply(get_m5_vp ).reset_index().pivot('date','order_book_id')
    df.columns = df.columns.get_level_values(1)
    return df

DAY_CORR_VP = UserDefinedLeafFactor('DAY_CORR_VP', get_day_corr_vp)
corr_vp_5m_d21_v2 = SUM(DAY_CORR_VP,21)

方案2(单纯为了理解 推荐使用方案一 夜盘归属问题这个方案未修复)
确定该因子为滑动窗口因子(RollingWindowFactor)
其实5分钟的行情数据对于这个滑动窗口因子并不是 基础因子,正真的基础因子是DATE,所以对DATE因子进行封装
把求和 求相关性的过程放在RollingWindowFactor的转换方法中
实现代码如下

import rqdatac
from rqfactor.extension import UserDefinedLeafFactor,CombinedRollingWindowFactor
from rqalpha_plus.apis import *
rqdatac.init()

global corr_vp_5m_d21_codes
global corr_vp_5m_d21_executed_codes
corr_vp_5m_d21_codes = []
corr_vp_5m_d21_executed_codes = []

def get_date(order_book_ids, start_date, end_date):
    """
    可以考虑改按每个code去获取真实的交易时间
    """
    print("get_date")
    global corr_vp_5m_d21_codes
    corr_vp_5m_d21_codes = order_book_ids
    dates = rqdatac.get_trading_dates(start_date, end_date)
    data = [ MT_util_date_str2int(str(date)) for date in dates] 
    dict_data = {code:data for code in order_book_ids}
    df = pd.DataFrame(
        index=pd.to_datetime(dates),
        data=dict_data)
    # print(corr_vp_5m_d21_codes)
    # print(df)
    print("get_date finish")
    return df

# 做了些全局操作 所以这个因子 只给CORR_VP使用
DATE = UserDefinedLeafFactor('DATE', get_date)


def get_vp(df):
    # data = pd.Series([3, -1, 4, 5, -2])
    l1_norm = np.linalg.norm(df['volume'], ord=1)
    a = df['volume']/l1_norm
    b = df['close']
    # 计算相关性系数
    correlation_matrix = np.corrcoef(a, b)
    # 获取 a 和 b 之间的相关性系数
    correlation_coefficient = correlation_matrix[0, 1]
    return correlation_coefficient


# 每个code 都会调用一次 考虑自己维护code 避免重复计算
def rolling_corr_vp(f_date,window):
    print("rolling_corr_vp")
    global corr_vp_5m_d21_codes
    global corr_vp_5m_d21_executed_codes
    # 按window分组
    rolling_dates = rolling_window(f_date, window)
    # 获取tradedate
    #print(rolling_dates)   
    cal_code = None
    for code in corr_vp_5m_d21_codes:
        if code not in corr_vp_5m_d21_executed_codes:
            cal_code = code
            corr_vp_5m_d21_executed_codes.append(code)
            break
    vps = []
    if cal_code:
        for dates in rolling_dates:
            start_date = str(dates[0])
            end_date = str(dates[-1])
            # print(start_date,end_date)
            # 分别获取组内的5分钟数据
            print(cal_code)
            data = rqdatac.get_price(cal_code, start_date, end_date, fields=['volume','close'], frequency='5m', adjust_type='none',expect_df=False)
            if not data:
                print(f" rqdatac.get_price {cal_code} {start_date}-{end_date} 数据为空")
                vps.append(np.nan)
                continue
            data['date'] = data.index.date
            r = data.groupby('date').apply(get_vp)
            vp = sum(r)
            vps.append(vp)
    return vps

def CORR_VP(window):
    return RollingWindowFactor(rolling_corr_vp,window,DATE)

corr_vp_5m_d21 = CORR_VP(21)

展望

本节介绍完多因子理论,因子构造以及米框因子框架方面的基础知识, 下一节开始,将深入期货多因子进行研究。