def __init__(self, *codes, start="20200101", end=yesterday(), col="close"): """ :param codes: Union[str, tuple], 格式与 :func:`xalpha.universal.get_daily` 相同,若需要汇率转换,需要用 tuple,第二个元素形如 "USD" :param start: %Y%m%d :param end: %Y%m%d, default yesterday """ totdf = pd.DataFrame() codelist = [] for c in codes: if isinstance(c, tuple): code = c[0] currency = c[1] else: code = c currency = "CNY" # 标的不做汇率调整 codelist.append(code) df = xu.get_daily(code, start=start, end=end) df = df[df.date.isin(opendate)] currency_code = _get_currency_code(currency) if currency_code: cdf = xu.get_daily(currency_code, start=start, end=end) cdf = cdf[cdf["date"].isin(opendate)] df = df.merge(right=cdf, on="date", suffixes=("_x", "_y")) df[col] = df[col + "_x"] * df[col + "_y"] df[code] = df[col] / df.iloc[0][col] df = df.reset_index() df = df[["date", code]] if "date" not in totdf.columns: totdf = df else: totdf = totdf.merge(on="date", right=df) self.totdf = totdf self.codes = codelist
def daily_increment(code, date, lastday=None, _check=False): """ 单一标的 date 日(若 date 日无数据则取之前的最晚有数据日,但该日必须大于 _check 对应的日期)较上一日或 lastday 的倍数, lastday 支持不完整,且不能离 date 太远 :param code: :param date: :param lastday: 如果用默认 None,则表示和前一日的涨跌 :param _check: 数据必须已更新到 date 日,除非之前每天都是节假日 :return: """ try: tds = xu.get_daily(code=code, end=date, prev=30) except Exception as e: # 只能笼统 catch 了,因为抓取失败的异常是什么都能遇到。。。 code = get_alt(code) if code: tds = xu.get_daily(code=code, end=date, prev=30) else: raise e tds = tds[tds["date"] <= date] if _check: date = date.replace("-", "").replace("/", "") date_obj = dt.datetime.strptime(date, "%Y%m%d") while tds.iloc[-1]["date"] < date_obj: # in case data is not up to date # 但是存在日本市场休市时间不一致的情况,估计美股也存在 if not is_on( date_obj.strftime("%Y%m%d"), get_market(code), no_trading_days=no_trading_days, ) or (date_obj.strftime("%Y-%m-%d") in gap_info.get(code, [])): print("%s is closed on %s" % (code, date)) if not lastday: return 1 # 当日没有涨跌,这里暂时为考虑 _check 和 lastday 相同的的情形 date_obj -= dt.timedelta(days=1) else: raise DateMismatch( code, reason="%s has no data newer than %s" % (code, date_obj.strftime("%Y-%m-%d")), ) if not lastday: ratio = tds.iloc[-1]["close"] / tds.iloc[-2]["close"] else: tds2 = tds[tds["date"] <= lastday] # 未考虑连 lastday 的数据数据源都没更新的情形,这种可能极小 ratio = tds.iloc[-1]["close"] / tds2.iloc[-1]["close"] return ratio
def __init__(self, code, status, name=None): """ :param code: str. 代码格式与 :func:`xalpha.universal.get_daily` 要求相同 :param status: 记账单或 irecord 类。 :param name: Optional[str]. 可提供标的名称。 """ self.code = code if isinstance(status, irecord): self.status = status.filter(code) else: self.status = status[status.code == code] # self.cftable = pd.DataFrame([], columns=["date", "cash", "share"]) try: self.price = xu.get_daily( self.code, start=self.status.iloc[0]["date"].strftime("%Y-%m-%d")) self.price["netvalue"] = self.price["close"] except Exception as e: logger.warning("%s when trade trying to get daily price of %s" % (e, self.code)) self.price = None self._arrange() if not name: try: self.name = get_rt(code)["name"] except: self.name = code self.type_ = None
def xirrcal(cftable, trades, date, guess=0.1): """ calculate the xirr rate :param cftable: cftable (pd.Dateframe) with date and cash column :param trades: list [trade1, ...], every item is an trade object, whose shares would be sold out virtually :param date: string of date or datetime object, the date when virtually all holding positions being sold :param guess: floating number, a guess at the xirr rate solution to be used as a starting point for the numerical solution :returns: the IRR as a single floating number """ date = convert_date(date) partcftb = cftable[cftable["date"] <= date] if len(partcftb) == 0: return 0 cashflow = [(row["date"], row["cash"]) for i, row in partcftb.iterrows()] rede = 0 for fund in trades: if not isinstance(fund, itrade): rede += fund.aim.shuhui( fund.briefdailyreport(date).get("currentshare", 0), date, fund.remtable[fund.remtable["date"] <= date].iloc[-1].rem, )[1] else: # 场内交易 pricedf = get_daily(fund.code, end=date.strftime("%Y%m%d"), prev=20) price = pricedf[pricedf.date <= date].iloc[-1]["close"] rede += fund.cftable.share.sum() * price cashflow.append((date, rede)) return xirr(cashflow, guess)
def __init__(self, code, start=None, end=None): """ :param code: str. 形式可以是 399971.XSHE 或者 SH000931 :param start: Optional[str]. %Y-%m-%d, 估值历史计算的起始日。 :param end: Dont use, only for debug """ yesterday_str = (dt.datetime.now() - dt.timedelta(days=1)).strftime("%Y-%m-%d") if len(code.split(".")) == 2: self.code = code self.scode = _convert_code(code) else: self.scode = code self.code = _inverse_convert_code(self.scode) if self.code in self.indexs: self.name = self.indexs[self.code][0] if not start: start = self.indexs[self.code][1] else: try: self.name = get_rt(self.scode)["name"] except: self.name = self.scode if not start: start = "2012-01-01" # 可能会出问题,对应指数还未有数据 self.start = start if not end: end = yesterday_str self.df = xu.get_daily("peb-" + self.scode, start=self.start, end=end) self.ratio = None self.title = "指数" self._gen_percentile()
def __init__(self, code, start=None, end=None, prev=None): """ :param code: str. eg SH501018, SZ160416 :param start: date range format is the same as xa.get_daily :param end: :param prev: """ self.code = code df1 = xu.get_daily("F" + self.code[2:], start=start, end=end, prev=prev) df2 = xu.get_daily(self.code, start=start, end=end, prev=prev) df1 = df1.merge(df2, on="date", suffixes=("_F", "_" + code[:2])) df1["diff_rate"] = ((df1["close_" + code[:2]] - df1["close_F"]) / df1["close_F"] * 100) self.df = df1
def fluctuation(self): if not self.ratio: d = self.df.iloc[-1]["date"] oprice = xu.get_daily(code=self.scode, end=d.strftime("%Y%m%d"), prev=20).iloc[-1]["close"] nprice = get_rt(self.scode)["current"] self.ratio = nprice / oprice return self.ratio
def get_portfolio(self, date=yesterdayobj()): """ 获取基金组合底层资产大类配置的具体值 :param date: :return: Dict[str, float]. stock,bond,cash 对应总值的字典 """ d = {"stock": 0, "bond": 0, "cash": 0} date = convert_date(date) for f in self.fundtradeobj: value = f.briefdailyreport(date).get("currentvalue", 0) if value > 0: if isinstance(f, itrade): if f.get_type() == "股票": d["stock"] += value continue elif f.get_type() in ["可转债", "债券"]: d["bond"] += value continue elif f.get_type() == "货币基金": d["cash"] += value continue elif f.get_type() == "场内基金": code = f.code[2:] else: continue else: code = f.code if code == "mf": d["cash"] += value continue if get_fund_type(code) == "货币基金": d["cash"] += value continue df = xu.get_daily("pt-F" + code, end=date.strftime("%Y%m%d")) if df is None or len(df) == 0: logger.warning("empty portfolio info for %s" % code) row = df.iloc[-1] if row["bond_ratio"] + row["stock_ratio"] < 10: # 联接基金 d["stock"] += ( (100 - row["bond_ratio"] - row["cash_ratio"]) * value / 100 ) d["bond"] += row["bond_ratio"] * value / 100 d["cash"] += row["cash_ratio"] * value / 100 else: d["stock"] += row["stock_ratio"] * value / 100 d["bond"] += row["bond_ratio"] * value / 100 d["cash"] += row["cash_ratio"] * value / 100 return d
def __init__(self, code, start=None, end=None): self.code = code self.scode = code if not end: end = (dt.datetime.now() - dt.timedelta(days=1)).strftime("%Y-%m-%d") if not start: start = "2016-01-01" # 基金历史通常比较短 self.start = start self.df = xu.get_daily("peb-" + code, start=start, end=end) self.name = get_rt(code)["name"] self.title = "基金" self.ratio = None self._gen_percentile()
def get_t0(self, return_date=True, percent=False): last_value, last_date = self.get_t1() last_date_obj = dt.datetime.strptime(last_date, "%Y-%m-%d") cday = last_onday(self.today) while last_date_obj < cday: # 昨天净值数据还没更新 # 是否存在部分部分基金可能有 gap? if cday.strftime("%Y-%m-%d") not in gap_info[self.fcode]: self.t1_type = "昨日未出" raise DateMismatch( self.code, reason="%s netvalue has not been updated to yesterday" % self.code, ) else: cday = last_onday(cday) # 经过这个没报错,就表示数据源是最新的 if last_date_obj >= self.today: # 今天数据已出,不需要再预测了 print("no need to predict net value since it has been out for %s" % self.code) self.t1_type = "今日已出" if not return_date: return last_value else: return last_value, last_date t = 0 n = 0 today_str = self.today.strftime("%Y%m%d") for k, v in self.t0dict.items(): w = v t += w r = get_rt(k) # k should support get_rt, investing pid doesn't support this! if percent: c = w / 100 * (1 + r["percent"] / 100) # 直接取标的当日涨跌幅 else: df = xu.get_daily(k) basev = df[df["date"] <= last_date].iloc[-1]["close"] c = w / 100 * r["current"] / basev currency_code = get_currency_code(k) if currency_code: c = c * daily_increment(currency_code, today_str) n += c n += (100 - t) / 100 t0value = n * last_value self.t0_delta = n if not return_date: return t0value else: return t0value, self.today.strftime("%Y-%m-%d")
def _base_value(self, code, shift): if not shift: funddf = xu.get_daily(code) ## 获取股指现货日线 return funddf[funddf["date"] < self.today.strftime( "%Y-%m-%d")].iloc[-1]["close"] # 日期是按当地时间 # TODO: check it is indeed date of last_on(today) else: if code not in self.bar_cache: funddf = get_bar(code, prev=48, interval="3600") ## 获取小时线 if self.now.hour > 6: # 昨日美国市场收盘才正常,才缓存参考小时线 self.bar_cache[code] = funddf else: funddf = self.bar_cache[code] return funddf[funddf["date"] <= self.today + dt.timedelta(hours=shift)].iloc[-1][ "close"] # 时间是按北京时间, 小时线只能手动缓存,日线不需要是因为自带透明缓存器
def __init__(self, code, start=None, end=None): """ :param code: str. 指数代码,eg. SH000016 :param start: :param end: """ df = xu.get_daily("teb-" + code, start=start, end=end) df["e"] = pd.to_numeric(df["e"]) df["b"] = pd.to_numeric(df["b"]) df["lnb"] = df["b"].apply(lambda s: np.log(s)) df["lne"] = df["e"].apply(lambda s: np.log(s)) df["roe"] = df["e"] / df["b"] * 100 df["date_count"] = (df["date"] - df["date"].iloc[0]).apply(lambda s: int(s.days)) self.df = df self.fit(verbose=False)
def get_netvalue(self, date=yesterdayobj()): # 若使用请务必配合带 precached 的缓存策略!! if not getattr(self, "fetchonly", None): self.fetchonly = False prestart = self.status.iloc[0]["date"] df = xu.get_daily( self.code, end=date.strftime("%Y%m%d"), prev=20, fetchonly=self.fetchonly, precached=prestart.strftime("%Y%m%d"), ) self.fetchonly = True # 这么做防止数据空白时期的反复抓取 if len(df) > 0: return df.iloc[-1].close else: return 0
def _base_value(self, code, shift): if not shift: funddf = xu.get_daily(code) ## 获取股指现货日线 return funddf[funddf["date"] <= last_onday(self.today)].iloc[-1][ "close"] # 日期是按当地时间 # TODO: check it is indeed date of last_on(today) else: if code not in self.bar_cache: funddf = get_bar(code, prev=168, interval="3600") ## 获取小时线 ## 注意对于国内超长假期,prev 可能还不够 if self.now.hour > 6: # 昨日美国市场收盘才正常,才缓存参考小时线 self.bar_cache[code] = funddf else: funddf = self.bar_cache[code] refdate = last_onday(self.today) + dt.timedelta(days=1) # 按北京时间校准 return funddf[funddf["date"] <= refdate + dt.timedelta(hours=shift)].iloc[-1][ "close"] # 时间是按北京时间, 小时线只能手动缓存,日线不需要是因为自带透明缓存器
def __init__(self, code, start=None, end=None): """ :param code: 801180 申万行业指数 :param start: :param end: """ self.code = code self.scode = code if not end: end = (dt.datetime.now() - dt.timedelta(days=1)).strftime("%Y-%m-%d") if not start: start = "2012-01-01" self.start = start self.df = xu.get_daily("sw-" + code, start=start, end=end) self.name = self.df.iloc[0]["name"] self.ratio = 1 self.title = "申万行业指数" self._gen_percentile()
def _arrange(self): d = {"date": [], "cash": [], "share": []} for _, r in self.status.iterrows(): d["date"].append(r.date) if r.share == 0: d["cash"].append(-r.value) d["share"].append(0) else: if r.value < 0: r.value = xu.get_daily( self.code, end=r.date.strftime("%Y-%m-%d"), prev=15 ).iloc[-1]["close"] if r.value == 0: d["cash"].append(0) d["share"].append(r.share) # 直接记录总的应增加+或减少的份额数 else: d["cash"].append(-r.value * r.share - abs(r.fee)) # 手续费总是正的,和买入同号 d["share"].append(r.share) self.cftable = pd.DataFrame(d)
def get_netvalue(self, date=yesterdayobj()): return get_daily(self.code, end=date.strftime("%Y%m%d"), prev=20).iloc[-1].close
def _is_on(code, date): df = xu.get_daily(code, prev=20, end=date) if len(df[df["date"] == date]) == 0: return False return True
def process_byday(self, date): if not date: self.date_obj = dt.datetime.today() else: self.date_obj = dt.datetime.strptime( date.replace("-", "").replace("/", ""), "%Y%m%d") if not date: rt = get_rt(self.code) self.name = rt["name"] self.cbp = rt["current"] # 转债价 self.stockp = get_rt(self.scode)["current"] # 股票价 else: try: if not self.name: rt = get_rt(self.code) self.name = rt["name"] except: self.name = "unknown" df = xu.get_daily(self.code, prev=100, end=self.date_obj.strftime("%Y%m%d")) self.cbp = df.iloc[-1]["close"] df = xu.get_daily(self.scode, prev=100, end=self.date_obj.strftime("%Y%m%d")) self.stockp = df.iloc[-1]["close"] df = xu.get_daily(self.scode, prev=360, end=self.date_obj.strftime("%Y%m%d")) self.history_volatility = np.std( np.log(df["close"] / df.shift(1)["close"])) * np.sqrt(244) if not self.refvolatility: self.volatility = 0.17 if self.rating in ["A", "A+", "AA-"]: self.volatility = 0.19 elif self.rating in ["AA"]: self.volatility = 0.18 if self.history_volatility < 0.25: self.volatility -= 0.01 elif self.history_volatility > 0.65: self.volatility += 0.02 elif self.history_volatility > 0.45: self.volatility += 0.01 self.years = len(self.rlist) - 1 syear = int(self.enddate.split("-")[0]) - self.years self.issuedate = str(syear) + self.enddate[4:] self.days = (dt.datetime.strptime(self.enddate, "%Y-%m-%d") - self.date_obj).days if not self.refbondrate: ratestable = get_bond_rates(self.rating, self.date_obj.strftime("%Y-%m-%d")) if self.rating in ["A", "A+", "AA-"]: ## AA 到 AA- 似乎是利率跳高的一个坎 cutoff = 2 else: cutoff = 4 if self.days / 365 > cutoff: # 过长久期的到期收益率,容易造成估值偏离,虽然理论上是对的 # 考虑到国内可转债市场信用风险较低,不应过分低估低信用债的债券价值 self.bondrate = ( ratestable[ratestable["year"] <= cutoff].iloc[-1]["rate"] / 100) else: self.bondrate = (ratestable[ratestable["year"] >= self.days / 365].iloc[0]["rate"] / 100) if not self.refriskfreerate: ratestable = get_bond_rates("N", self.date_obj.strftime("%Y-%m-%d")) if self.days / 365 > 5: self.riskfreerate = ( ratestable[ratestable["year"] <= 5].iloc[-1]["rate"] / 100) else: self.riskfreerate = ( ratestable[ratestable["year"] >= self.days / 365].iloc[0]["rate"] / 100)
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], )