Ejemplo n.º 1
0
def get_data(code,
             field,
             startDate,
             endDate,
             add_stockcode=True,
             highFreq=False,
             highFreqBarSize='barSize=5',
             priceAdj=False,
             time_std=True):
    '''
    从wind中获取数据,可以获取高频率(1天及以上)和低频率(1分钟-1天内)的数据,数据以字典的形式返回
    @param:
        code: 需要获取数据的Wind代码,必须为字符串型
        field: 需要获取数据的类型,可以为列表或者元组或者字符串类型,例如['close', 'open', 'high',
            'low']等价于('close', 'open', 'high', 'low')等价于'close, open, high, low',常用的数据类型如
            {close: 收盘价,open: 开盘价,high: 最高价,low: 最低价,volume: 成交量},其他可参考MATLAB中的
            w.menu wsi和wsd来查询
        startDate: 需要数据的开始时间,可以为datetime.datetime类型,也可以是字符串类型,如果是高频率
            的数据,应该是用datetime.datetime类型提供,且提供的时间最好精确到分钟
        endDate: 需要数据的结束时间,与startDate的要求相同
        add_stockcode: 是否在返回的DataFrame中添加股票代码列,默认为添加(True)
        highFreq: 表示是否需要取高频率的数据,bool型
        highFreqBarSize: 提取的高频率数据的频率设定,默认为5分钟线,即barSize=5,其他设置可以类似,
            但是要求必须为字符串,形式只能为barSize=n,n为需要的数据的频率
        priceAdj: 是否需要复权,默认不需要,bool型,只能对有复权选项的证券使用,否则返回的数据会有错误
        time_std: 是否将时间由Wind格式转换为正常格式,默认为True
    @return:
        data: pd.DataFrame格式数据,其中另外会添加time列,记录时间
    '''
    check_connection()
    if highFreq:
        if priceAdj:
            rawData = w.wsi(code, field, startDate, endDate, highFreqBarSize,
                            'PriceAdj=F')
        else:
            rawData = w.wsi(code, field, startDate, endDate, highFreqBarSize)
    else:
        if priceAdj:
            rawData = w.wsd(code, field, startDate, endDate, 'PriceAdj=F')
        else:
            rawData = w.wsd(code, field, startDate, endDate)
    assert rawData.ErrorCode == 0, rawData.Data[0][0]
    data = dict(zip(field, rawData.Data))
    data['time'] = rawData.Times
    data = pd.DataFrame(data)
    if add_stockcode:
        data['code'] = [code] * len(data)
    if time_std:
        import dateshandle
        data['time'] = dateshandle.wind_time_standardlization(data.time)
    return data
Ejemplo n.º 2
0
def cal_nav(holdings,
            end_date,
            quotes,
            ini_capital=1e9,
            normalize=True,
            cal_to=False,
            **kwargs):
    '''
    根据持仓状况计算策略的净值(以每个交易日的收盘价计算)
    @param:
        holdings: 由get_daily_holding返回的每个换仓日的持仓,或者为OrderDict类型,键为换仓日日期,
            值为对应的持仓(为PositionGroup类型)
        end_date: 最后一个换仓记录的结束时间,一般情况下,应该设置为最后一个在平衡日,可以为
            pd.to_datetime可以解析的任何类型
        quotes: 行情数据
        ini_capital: 初始资本,默认为10亿人民币,避免数额过小导致在整数约束下很多股票的数量为0
        normalize: 是否将组合的价值由金额转换为净值(转换方法为组合市场价值除以初始资本),
            默认为True,即需要转换
        cal_to: 是否计算换手率,默认为False
        kwargs: 一些其他的参数,用于传入build_pos函数
    @return:
        df类型,索引为时间,列名为group_i,其中i为分组次序
    '''
    # 对交易日与换仓日之间的关系进行映射
    start_date = min(holdings.keys())  # 开始日一定为换仓日,且换仓日一定为交易日
    tds = dateshandle.wind_time_standardlization(
        dateshandle.get_tds(start_date, end_date))
    tds_df = pd.DataFrame({'chg_date': list(holdings.keys())})
    tds_df['tds'] = tds_df['chg_date']
    tds_df = tds_df.set_index('tds').sort_index()
    tds_df = tds_df.reindex(tds, method='ffill')
    tds_map = dict(zip(tds_df.index, tds_df.chg_date))

    # 初始化
    portfolio_record = None  # 组合记录
    nav = list()  # 净值结果
    cols = ['time'] + [
        'group_%02d' % i for i in range(1,
                                        len(holdings[start_date]) + 1)
    ]  # 结果列名
    turnover = list()  # 换手率
    # 交易日循环
    for td, tq_idx in zip(sorted(tds_map), tqdm(tds_map)):
        # 当前为换仓日,第一个建仓日一定为换仓日
        if tds_map[td] == td:
            cur_pos = holdings[td]
            if portfolio_record is None:  # 第一次建仓
                portfolio_record = [
                    Portfolio(pd.DataFrame({
                        'code': [],
                        'num': []
                    }), ini_capital) for i in range(len(cur_pos))
                ]
            tmp_portrecord = list()
            for port_idx, pos in enumerate(cur_pos):
                # 此处建仓实际上假设之前的股票都在今天开盘卖出,然后再按照开盘价买入新的仓位
                # 虽然这种假设不合理,但是如果使用上个交易日的数据会带来一些复杂性,且一个交易日的
                # 收盘价和随后一天的开盘价的差值一般不会太大,故忽略这方面的影响
                tmp_port = build_pos(
                    pos,
                    portfolio_record[port_idx].mkt_value(quotes, td, 'open'),
                    quotes, td, **kwargs)
                tmp_portrecord.append(tmp_port)
            # TODO 在此处添加换手率计算,为了保证兼容性,可以考虑加入默认参数,默认不返回换手率
            tmp_to = dict()
            ports = zip(portfolio_record, tmp_portrecord)
            for port_idx, p in enumerate(ports):
                tmp_to['turnover_%02d' % (port_idx + 1)] = cal_turnover(
                    p[0], p[1], quotes, td)
            tmp_to['time'] = td
            turnover.append(tmp_to)
            portfolio_record = tmp_portrecord  # 更新portfolio_record
        # 计算每个组合的收盘市场价值
        tmp_mktvalue = list()
        for port in portfolio_record:
            mkt_value = port.mkt_value(quotes, td)
            tmp_mktvalue.append(mkt_value)
        tmp_mktvalue.insert(0, td)
        nav.append(dict(zip(cols, tmp_mktvalue)))
    nav = pd.DataFrame(nav)
    nav = nav.set_index('time')
    if normalize:
        for i in range(1, len(holdings[start_date]) + 1):
            nav['group_%02d' % i] = nav['group_%02d' % i] / ini_capital
    if cal_to:
        turnover = pd.DataFrame(turnover)
        return nav, turnover
    return nav
Ejemplo n.º 3
0
def get_daily_holding(signal_data, quotes_data, stock_pool, industry_cls,
                      stock_filter, rebalance_dates):
    '''
    用于根据一定的条件,从股票池中选出满足一定条件的股票,然后将其映射到这个期间的交易日中,
    最终得到每个交易日的持仓
    @param:
        signal_data: 信号数据DataFrame,必须包含time、code列
        quotes_data: 行情数据DataFramem,必须包含time、code列
        stock_pool: 时点股票池DataFrame,测试中股票池为离当前交易日最近的一个时点的股票池,
            必须包含time、code列;可以为None,当参数为None时,不加上股票池的限制
        industry_cls: 行业分类DataFrame,必须包含time、code列
        stock_filter: 用于选择股票的函数,形式为stock_filter(cur_signal_data, cur_ind_cls),要求返回
            的股票为[[code1, code2, ...], [codex1, codex2, ...], ...]
        rebalance_dates: 再平衡日,即在该日期计算下个时期的股票池,然后在下个最近的交易日换仓
    @return:
        换仓日的持仓,格式为字典类型,字典值为PositionGroup类型,因此需要注意返回的持仓并没有时间
        顺序,需要先对键进行排序
    注:
        对于每个再平衡日,计算指标,筛选股票,然后在下个交易日换仓,随后的交易日的持仓都与该
        新的持仓相同,直至到下个新的在平衡日
    '''
    # 获取交易日
    start_time, end_time = rebalance_dates[0], rebalance_dates[-1]
    tds = dateshandle.wind_time_standardlization(
        dateshandle.get_tds(start_time, end_time))

    # 计算对应的换仓日
    # 计算再平衡日对应的下标,最后一个交易日没有换仓日
    reb_index = [tds.index(t) for t in rebalance_dates[:-1]]
    # 计算换仓日对应的日期
    chg_dates = [tds[i + 1] for i in reb_index]
    key_dates = list(zip(rebalance_dates[:-1], chg_dates))

    # 初始化
    holdings = dict()
    # stockpool_bydate = stock_pool.groupby('time')

    # 计算换仓日的股票组合
    for (reb_dt, chg_dt), tqi in zip(key_dates, tqdm(key_dates)):
        # 获取再平衡日的行业分类,减少不必要的数据加载
        if industry_cls is None:
            ind_cls = None
        else:
            # ind_cls = industry_cls.loc[industry_cls.time == reb_dt]
            ind_cls = get_industrycls(industry_cls, reb_dt)
        # 过滤不能交易的股票,此处会自动将建仓日不存在数据的股票过滤
        tradeable_stocks = quotes_data.loc[(quotes_data.time == chg_dt) &
                                           (~quotes_data.STTag)
                                           & quotes_data.tradeable,
                                           'code'].tolist()

        # 获取换仓日股票池,如果传入的股票池参数为None,则返回所有满足要求的股票
        if stock_pool is not None:
            constituent = get_constituent(stock_pool, chg_dt)
            tradeable_stocks = set(tradeable_stocks).intersection(constituent)

        # 获取当前信号数据,加入指数成份过滤,更新pandas的版本(0.20.1)后发现此步骤速度特别慢
        reb_sig_data = signal_data.loc[(signal_data['time'] == reb_dt) & (
            signal_data['code'].isin(tradeable_stocks))]
        # 根据信号函数计算当前的股票组
        valid_stocks = stock_filter(reb_sig_data, ind_cls)
        # valid_stocks = [[c for c in group if c in tradeable_stocks]
        #                 for group in valid_stocks]
        holdings[chg_dt] = PositionGroup(valid_stocks)
    return holdings