def get_holdings_dict(code, aim=95): """ 通过天天基金的股票持仓数据来生成实时预测所需的持仓字典,不保证稳定性和可靠性以及 API 的连续性,慎用 :param code: :param aim: :return: """ df = get_fund_holdings(code) if df.ratio.sum() < 60: d = dt.datetime.now() if d.month > 3 and d.month < 8: year = d.year - 1 season = 4 elif d.month <= 3: year = d.year - 1 season = 2 else: year = d.year season = 2 # season 只选 2,4, 具有更详细的持仓信息 df = get_fund_holdings(code, year, season) if df is None: if season == 4: season = 2 else: year -= 1 season = 4 df = get_fund_holdings(code, year, season) df["scode"] = df["code"].apply(ttjjcode) d = pd.Series(df.ratio.values, index=df.scode).to_dict() d = scale_dict(d, aim=aim) return d
def benchmark_test(self, start, end, **kws): """ 对该净值预测模型回测 :param start: str. 起始日期 :param end: str. 终止日期 :param kws: 可选仓位估计的超参。 :return: pd.DataFrame. real 列为真实涨跌幅,est 列为估计涨跌幅,diff 列为两者之差。 """ compare_data = { "date": [], } l = kws.get("window", 4) q = kws.get("decay", 0.8) c = kws.get("pos", self.position_zero) s = kws.get("smooth", _smooth_pos) real_holdings = {self.fcode: 100} full_holdings = scale_dict(self.t1dict.copy(), aim=100) compare_data["est"] = [] compare_data["real"] = [] compare_data["estpos3"] = [] compare_data["estpos1"] = [] fq = deque([c / 100] * l, maxlen=l) current_pos = c / 100 dl = pd.Series(pd.date_range(start=start, end=end)) dl = dl[dl.isin(opendate)] for j, d in enumerate(dl): if j == 0: continue dstr = d.strftime("%Y%m%d") lstdstr = dl.iloc[j - 1].strftime("%Y%m%d") compare_data["date"].append(d) fullestf = evaluate_fluctuation(full_holdings, dstr, lstdstr) realf = evaluate_fluctuation(real_holdings, dstr, lstdstr) estf = fullestf * current_pos compare_data["est"].append(estf) compare_data["estpos3"].append(current_pos) compare_data["estpos1"].append(fq[-1]) compare_data["real"].append(realf) pos = s(realf, fullestf, fq[-1]) fq.append(pos) fq[0] = c / 100 ## 模拟实际的无状态仓位分析 if self.positions: current_pos = sum([q**i * fq[l - i - 1] for i in range(l) ]) / sum([q**i for i in range(l)]) if current_pos > 1: current_pos = 1 cpdf = pd.DataFrame(compare_data) cpdf["diff"] = cpdf["est"] - cpdf["real"] self.cpdf = cpdf return cpdf
def get_position(self, date=None, refresh=False, return_date=True, **kws): """ 基于 date 日之前的净值数据,对 date 预估需要的仓位进行计算。 :param date: str. %Y-%m-%d :param refresh: bool, default False. 若为 True,则刷新缓存,重新计算仓位。 :param return_date: bool, default True. return tuple, the second one is date in the format %Y%m%d :param kws: 一些预估仓位可能的超参。包括 window,预估所需的时间窗口,decay 加权平均的权重衰减,smooth 每日仓位处理的平滑函数。以上参数均可保持默认即可获得较好效果。 :return: float. 0-100. 100 代表满仓。 """ if not date: date = last_onday(self.today).strftime("%Y%m%d") else: date = date.replace("/", "").replace("-", "") if date not in self.position_cache or refresh: fdict = scale_dict(self.t1dict.copy(), aim=100) l = kws.get("window", 4) q = kws.get("decay", 0.8) s = kws.get("smooth", _smooth_pos) d = dt.datetime.strptime(date, "%Y%m%d") posl = [sum([v for _, v in self.t1dict.items()]) / 100] for _ in range(l): d = last_onday(d) for _ in range(l - 1): d = next_onday(d) pred = evaluate_fluctuation( fdict, d.strftime("%Y-%m-%d"), lastday=last_onday(d).strftime("%Y-%m-%d"), ) real = evaluate_fluctuation( {self.fcode: 100}, d.strftime("%Y-%m-%d"), lastday=last_onday(d).strftime("%Y-%m-%d"), ) posl.append(s(real, pred, posl[-1])) current_pos = sum([q**i * posl[l - i - 1] for i in range(l) ]) / sum([q**i for i in range(l)]) self.position_cache[date] = current_pos if not return_date: return self.position_cache[date] else: return ( self.position_cache[date], date[:4] + "-" + date[4:6] + "-" + date[6:8], )
def get_t1(self, date=None, return_date=True): """ 预测 date 日的净值,基于 date-1 日的净值和 date 日的外盘数据,数据自动缓存,不会重复计算 :param date: str. %Y-%m-%d. 注意若是 date 日为昨天,即今日预测昨日的净值,date 取默认值 None。 :param return_date: bool, default True. return tuple, the second one is date in the format %Y%m%d :return: float, (str). :raises NonAccurate: 由于外盘数据还未及时更新,而 raise,可在调用程序中用 except 捕获再处理。 """ if date is None: yesterday = last_onday(self.today) datekey = yesterday.strftime("%Y%m%d") else: datekey = date.replace("/", "").replace("-", "") if datekey not in self.t1value_cache: if self.positions: current_pos = self.get_position(datekey, return_date=False) hdict = scale_dict(self.t1dict.copy(), aim=current_pos * 100) else: hdict = self.t1dict.copy() if date is None: # 此时预测上个交易日净值 yesterday_str = datekey last_value, last_date = self.get_t2() last_date_obj = dt.datetime.strptime(last_date, "%Y-%m-%d") cday = last_onday(last_onday(self.today)) while last_date_obj < cday: # 前天净值数据还没更新 # 是否存在部分 QDII 在 A 股交易日,美股休市日不更新净值的情形? if (cday.strftime("%Y-%m-%d") not in gap_info[self.fcode]) and is_on( cday, "US", no_trading_days): # 这里检查比较宽松,只要当天美股休市,就可以认为确实基金数据不存在而非未更新 self.t1_type = "前日未出" raise DateMismatch( self.code, reason= "%s netvalue has not been updated to the day before yesterday" % self.code, ) else: cday = last_onday(cday) # 经过这个没报错,就表示数据源是最新的 if last_date_obj >= last_onday(self.today): # 昨天数据已出,不需要再预测了 print( "no need to predict t-1 value since it has been out for %s" % self.code) self.t1_type = "昨日已出" self.t1value_cache = { last_date.replace("-", ""): last_value } if not return_date: return last_value else: return last_value, last_date else: yesterday_str = datekey fund_price = xu.get_daily(self.fcode) # 获取国内基金净值 fund_last = fund_price[fund_price["date"] < date].iloc[-1] # 注意实时更新应用 date=None 传入,否则此处无法保证此数据是前天的而不是大前天的,因为没做校验 # 事实上这里计算的预测是针对 date 之前的最晚数据和之前一日的预测 last_value = fund_last["close"] last_date = fund_last["date"].strftime("%Y-%m-%d") self.t1_delta = (1 + evaluate_fluctuation( hdict, yesterday_str, lastday=last_date, _check=True) / 100) net = last_value * self.t1_delta self.t1value_cache[datekey] = net self.t1_type = "已计算" if not return_date: return self.t1value_cache[datekey] else: return ( self.t1value_cache[datekey], datekey[:4] + "-" + datekey[4:6] + "-" + datekey[6:8], )