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