Warning: file_exists(): open_basedir restriction in effect. File(/www/wwwroot/com.xiximiao.oa/com.cmstop/public/www/wp-content/db.php) is not within the allowed path(s): (/www/wwwroot/com.xiximiao.oa/com.cmstop/public/www/:/tmp/:/proc/:/var/log/nginx/:/www/wwwroot/com.xiximiao.oa/com.cmstop/public/:/www/wwwroot/com.xiximiao.oa/com.cmstop/vendor/:/www/wwwroot/com.xiximiao.oa/com.cmstop/ppk/) in /www/wwwroot/com.xiximiao.oa/com.cmstop/public/www/wp-includes/load.php on line 707
2039 多因子策略(多元线性回归).py » 轻知量化 QMT、PTrade、聚宽策略分享交流平台

2039 多因子策略(多元线性回归).py

# 风险及免责提示:该策略由聚宽用户分享,仅供学习交流使用。
# 原文一般包含策略说明,如有疑问建议到原文和作者交流讨论。
# 克隆自聚宽文章:https://www.joinquant.com/post/25273
# 标题:因子分析系列文章(八):多因子策略(多元线性回归)
# 作者:量化狙击

# 请选择 python 2 来进行回测

# 多因子策略-APT模型(Multi-factor) 
import math
import datetime
import numpy as np
import pandas as pd
from jqdata import *
# 导入多元线性规划需要的工具包
import statsmodels.api as sm
from statsmodels import regression
import datetime as dt

'''
================================================================================
总体回测前
================================================================================
'''
#总体回测前要做的事情
def initialize(context):
    set_params()      # 设置策参数
    set_variables()   # 设置中间变量
    set_backtest()    # 设置回测条件
    

#设置策略参数
def set_params():
    g.tc = 5          # 设置调仓天数
    g.stocknum = 20   # 持有股票数量


#设置中间变量
def set_variables():
    g.t = 0                # 记录回测运行的天数
    g.if_trade = False     # 当天是否交易


#设置回测条件
def set_backtest():
    # 当前价格的百分比设置滑点
    set_slippage(PriceRelatedSlippage(0.002))   
    # 设置对比基准
    set_benchmark('000985.XSHG') 
    # 使用真实价格
    set_option('use_real_price', True)
    # 设置报错等级
    log.set_level('order', 'error')


'''
================================================================================
每天开盘前
================================================================================
'''
# 每天开盘前要做的事。计算tiaocang
def before_trading_start(context):
    if g.t%g.tc==0:
        #每g.tc天,交易一次
        g.if_trade=True 
        # 设置手续费与手续费
        set_slip_fee(context) 
    g.t += 1
    

# 设置可行股票池:
# 过滤掉当日停牌的股票,且筛选出前days天未停牌股票
# 输入:stock_list为list类型,样本天数days为int类型,context(见API)
# 输出:list
def set_feasible_stocks(stock_list,days,context):
    # 得到是否停牌信息的dataframe,停牌的1,未停牌得0
    suspened_info_df = get_price(list(stock_list), start_date=context.current_dt, end_date=context.current_dt, frequency='daily', fields='paused')['paused'].T
    # 过滤停牌股票 返回dataframe
    unsuspened_index = suspened_info_df.iloc[:,0]<1 # 得到当日未停牌股票的代码list: unsuspened_stocks = suspened_info_df[unsuspened_index].index # 进一步,筛选出前days天未曾停牌的股票list: feasible_stocks=[] current_data=get_current_data() for stock in unsuspened_stocks: if sum(attribute_history(stock, days, unit='1d',fields=('paused'),skip_paused=False))[0]==0: feasible_stocks.append(stock) return feasible_stocks # 根据不同的时间段设置滑点与手续费 def set_slip_fee(context): # 将滑点设置为0 set_slippage(FixedSlippage(0)) # 根据不同的时间段设置手续费 dt=context.current_dt if dt>datetime.datetime(2013,1, 1):
        set_commission(PerTrade(buy_cost=0.0003, sell_cost=0.0013, min_cost=5)) 
        
    elif dt>datetime.datetime(2011,1, 1):
        set_commission(PerTrade(buy_cost=0.001, sell_cost=0.002, min_cost=5))
            
    elif dt>datetime.datetime(2009,1, 1):
        set_commission(PerTrade(buy_cost=0.002, sell_cost=0.003, min_cost=5))
    else:
        set_commission(PerTrade(buy_cost=0.003, sell_cost=0.004, min_cost=5))


# 过滤股票,过滤停牌退市ST股票,选股时使用
def filter_stock_ST(stock_list):
    curr_data = get_current_data()
    for stock in stock_list:
        if (curr_data[stock].paused) or (curr_data[stock].is_st) or ('ST' in curr_data[stock].name)\
            or ('*' in curr_data[stock].name)\
            or ('退' in curr_data[stock].name):
            stock_list.remove(stock)
    return stock_list


# 交易时使用,过滤每日开盘时的涨跌停股filter low_limit/high_limit    
def filter_stock_limit(stock_list):  
    curr_data = get_current_data()
    for stock in stock_list:
        price = curr_data[stock].day_open
        if (curr_data[stock].high_limit <= price) or (price <= curr_data[stock].low_limit):
            stock_list.remove(stock)
    return stock_list


# 可选项: 过滤上市180日以内次新股
def remove_new_stocks(security_list,context):
    for stock in security_list:
        days_public = (context.current_dt.date() - get_security_info(stock).start_date).days
        if days_public < 180: security_list.remove(stock) return security_list # DF类型MAD数据清洗function,一次处理多列数据 def fun_normalizeData(df): for key in df.keys(): if (key != 'code'): mad = func_mad(df[key]) MA = np.mean(df[key]) for i in range(len(df[key])): x = df.loc[i,key] if (x > MA + 3*mad):
                    x = MA + 3*mad
                elif (x < MA - 3*mad): x = MA - 3*mad return df # 求MAD绝对中位值 def func_mad(myData): return np.mean(np.absolute(myData - np.mean(myData))) # 取得股票某个区间内的所有收盘价(用于取前interval日和当前日 收盘价) def getStockPrice(stock, interval): # 输入stock证券名,interval期 h = attribute_history(stock, interval, unit='1d', fields=('close'), skip_paused=True) return (h['close'].values[0] , h['close'].values[-1]) # 0是第一个(interval周期的值,-1是最近的一个值(昨天收盘价)) # 协助进行线性回归(SVR) # 输入:factors是所有股票的上一期因子值-list,returns是股票上一期的收益 # 支持list,array,DataFrame等三种数据类型 def linreg(factors,returns): # 加入一列常数列,表示市场的状态以及因子以外没有考虑到的因素 X = sm.add_constant(array(factors)) Y = array(returns) # 进行多元线性回归 results = regression.linear_model.OLS(Y, X).fit() # 进行SVR回归 # svr = SVR(kernel='rbf', gamma=0.1) # model = svr.fit(X, Y) # 返回多元线性回归的参数值 return results.params # return model.get_params() # 求dataframe 对于X的中性化后的df, X是df中的一个key def df_neutralization(df, X): for key in df.keys(): if (key!='code' and key!=X): y = df[key] x = df[X] df[key] = linres(y, x) return df # 求线性回归残差(中性化用途) def linres(y,x): y = array(y) x = sm.add_constant(array(x)) if len(y)>1:
        model = regression.linear_model.OLS(y,x).fit()
    return model.resid
    
    
'''
================================================================================
每天交易时
================================================================================
'''
def handle_data(context, data):

    # 每个调仓日截面上,进行数据提取和计算
    if g.if_trade == True:
        # 从000985中证全指中提取个股,由全部A股股票中剔除ST、*ST股票,不足3个月等股票后的剩余股票构成样本股
        sample = get_index_stocks('000985.XSHG', date = None)
        
        # 过滤停牌退市ST股票,次新股
        sample = filter_stock_ST(sample)
        sample = remove_new_stocks(sample,context)
        sample = filter_stock_limit(sample)
    
        # 获取基本面数据并作初步计算
        q = query(valuation.code, 
                    valuation.market_cap, # 总市值(亿元)
                    balance.total_assets / balance.total_liability, # 杠杆率
                    indicator.inc_net_profit_year_on_year, # 净利润同比增长率(质量成长)
                    indicator.inc_revenue_year_on_year, # 营业收入同比增长率(规模成长)
                    indicator.roe / valuation.pb_ratio, # PB-ROE
                    valuation.pe_ratio / indicator.inc_net_profit_year_on_year, # PEG
                    ).filter(
                        # 优选基本面质量较好的股票进行回归
                        valuation.pe_ratio / indicator.inc_net_profit_year_on_year>0,
                        valuation.pe_ratio / indicator.inc_net_profit_year_on_year<1,
                        valuation.code.in_(sample))
                    
        # 本期基本面数据df,并清洗
        df = get_fundamentals(q, date = None)
        df.dropna(axis=0,how='any') # 删除表中含有任何NaN的行
        # 打开后执行3倍MAD数据清洗
        # df = fun_normalizeData(df)
        # 上一期时间,context.current_dt是交易日,不是一般的日期,这非常重要!!
        pre_date = context.current_dt - dt.timedelta(days=g.tc+1)
        

   
        
       # print (pre_date)
        # 上一期基本面数据df_p,并清洗
        df_p = get_fundamentals(q, date = pre_date)
        df_p.dropna(axis=0,how='any') # 删除表中含有任何NaN的行
        # 打开后执行3倍MAD数据清洗
        # df_p = fun_normalizeData(df_p)
        
        # 求个股本周期回报值(个股名单在df.code列中),作为回归目标Y
        momentum = []
        # for i in sample:
        for i in list(df.code):    
            interval,Yesterday = getStockPrice(i, g.tc) 
            stock_momentum = Yesterday / interval - 1
            momentum.append((i, stock_momentum))
        # 转化成np.array结构,方便取出第二列数据(回报值)
        np_momentuma = np.array(momentum)
        momentum_value = np_momentuma[:,1].astype(float).tolist()
        df['momentum_value'] = momentum_value


        # ====================== 新增因子部分 开始 ========================
        # 此处可新增各类自定义因子
        # ====================== 新增因子部分 结束 ========================
    
        # 本期因子排列命名,并中性化(可选)
        df = df.dropna()
        df.columns = ['code','market_cap','LEV','INP_YOY','IR_YOY','ROE_PB', 'PE_G','momentum_value']
        df.index = df.code.values
        # df['momentum_value'] = np.log(df['momentum_value']) 
        # 市值中性化过程
        # df = df_neutralization(df,'market_cap')
        del df['code']
        
        # 上期因子排列命名,并中性化(可选)
        df_p = df_p.dropna()
        df_p.columns = ['code','market_cap','LEV','INP_YOY','IR_YOY','ROE_PB', 'PE_G']
        df_p.index = df_p.code.values
        # 市值中性化过程
        # df_p = df_neutralization(df_p,'market_cap')
        del df_p['code']

        # X_p是上一期的因子值部分,X是本期因子值部分,Y是回归目标
        X_p = df_p[['LEV','IR_YOY','INP_YOY','ROE_PB','PE_G']]
        X = df[['LEV','IR_YOY','INP_YOY','ROE_PB', 'PE_G']]
        Y = df[['momentum_value']]

        
        # 舍弃X_p和Y中不同的index(股票代码),如[u'000975.XSHE']
        # 先去除X_p比Y多的index
        diffIndex = X_p.index.difference(Y.index)   #包含在第一个集合中,但不包含在第二个集合
        # 删除整行
        X_p = X_p.drop(diffIndex,errors='ignore')
        X = X.drop(diffIndex,errors='ignore')
        Y = Y.drop(diffIndex,errors='ignore')
        
        # 然后去除Y比X_p多的index
        diffIndex = Y.index.difference(X_p.index)
        X_p = X_p.drop(diffIndex,errors='ignore')
        X = X.drop(diffIndex,errors='ignore')
        Y = Y.drop(diffIndex,errors='ignore')
        
        # 用上期因子值,和本期回报率,训练得到权重weights
        weights = linreg(X_p,Y)   #一元线性回归
        print (weights)
        # 带入本期因子值X,到股价公式,weights[1:]是从第二行开始所有行
        # print(weights)
        points = X.dot(weights[1:]) + weights[0]
       
        # 做降序排序,买入回归股价高的股票
        points.sort(ascending = False)
        print (points)        
        # 通过list函数将factor的index列转化成list数据类型,然后买入前g.stocknum个
        order_list = list(points.index[:g.stocknum])
        # 最终名单,分别下单给两个交易函数
        order_stock_sell(context,order_list)
        order_stock_buy(context,order_list)
        
    g.if_trade = False

2025-02-20
⚠️
本站资源大多来自网络,仅供网友学习交流,未经作者或上传书面授权,请勿作他用。
站长 vx: xiangyin615 或者 留言反馈 ,我们将尽快处理。
Notice: When you of the legal rights be violate, please stir to vx: xiangyin615
个人中心
购物车
优惠劵
搜索