def update(self): """ function to incrementally update the pricetable after fetch the old one """ lastdate = self.price.iloc[-1].date diffdays = (yesterdayobj() - lastdate).days if ( diffdays == 0 ): ## for some QDII, this value is 1, anyways, trying update is compatible (d+2 update) return None elif diffdays <= 10: self._updateurl = ( "http://fund.eastmoney.com/f10/F10DataApi.aspx?type=lsjz&code=" + self.code + "&page=1&per=" + str(diffdays)) con = _download(self._updateurl) soup = BeautifulSoup(con.text, "lxml") items = soup.findAll("td") elif ( diffdays > 10 ): ## there is a 20 item per page limit in the API, so to be safe, we query each page by 10 items only items = [] for pg in range(1, int(diffdays / 10) + 2): self._updateurl = ( "http://fund.eastmoney.com/f10/F10DataApi.aspx?type=lsjz&code=" + self.code + "&page=" + str(pg) + "&per=10") con = _download(self._updateurl) soup = BeautifulSoup(con.text, "lxml") items.extend(soup.findAll("td")) else: raise TradeBehaviorError( "Weird incremental update: the saved copy has future records") date = [] netvalue = [] totvalue = [] comment = [] for i in range(int(len(items) / 7)): ts = pd.Timestamp(items[7 * i].string) if (ts - lastdate).days > 0: date.append(ts) netvalue.append(float(items[7 * i + 1].string)) totvalue.append(float(items[7 * i + 2].string)) comment.append(_nfloat(items[7 * i + 6].string)) else: break df = pd.DataFrame({ "date": date, "netvalue": netvalue, "totvalue": totvalue, "comment": comment, }) df = df.iloc[::-1] ## reverse the time order df = df[df["date"].isin(opendate)] df = df.reset_index(drop=True) df = df[df["date"] <= yesterdayobj()] if len(df) != 0: self.price = self.price.append(df, ignore_index=True, sort=True) return df
def __init__(self, *fundtradeobj, status=None, fetch=False, save=False, path='', form='csv', totmoney=100000, cashobj=None): super().__init__(*fundtradeobj, status=status, fetch=fetch, save=save, path=path, form=form) if cashobj is None: cashobj = cashinfo() self.totmoney = totmoney nst = mulfix._vcash(totmoney, self.totcftable, cashobj) cashtrade = trade(cashobj, nst) # super().__init__(*self.fundtradeobj, cashtrade) self.fundtradeobj = list(self.fundtradeobj) self.fundtradeobj.append(cashtrade) self.fundtradeobj = tuple(self.fundtradeobj) btnk = bottleneck(self.totcftable) if btnk > totmoney: raise TradeBehaviorError('the initial total cash is too low') self.totcftable = pd.DataFrame(data={'date': [nst.iloc[0].date], 'cash': [-totmoney]})
def sell(self, code, share, date, is_value=False): """ :param code: :param share: :param date: datetime obj :param is_value: bool, default False. 货币基金可按照金额赎回 :return: """ share = abs(share) if self.verbose: print(f"sell {share} of {code} on {date.strftime('%Y-%m-%d')}") if code not in self.trades: raise TradeBehaviorError( "You are selling something that you don't have") df = self.trades[code].status cftable = self.trades[code].cftable cftable = cftable[cftable["date"] <= self.lastdates[code]] remtable = self.trades[code].remtable remtable = remtable[remtable["date"] <= self.lastdates[code]] self.lastdates[code] = date self.lastdates[code] = date df2 = pd.DataFrame([[date, -share]], columns=["date", self.get_code(code)]) df = df.append(df2) if is_value: self.set_fund(code, value_label=1) self.trades[code] = trade( self.infos[code], df, cftable=cftable, remtable=remtable, ) if is_value: self.set_fund(code, value_label=0)
def _addrow(self): """ Return cashflow table with one more line or raise an exception if there is no more line to add The same logic also applies to rem table 关于对于一个基金多个操作存在于同一交易日的说明:无法处理历史买入第一笔同时是分红日的情形, 事实上也不存在这种情形。无法处理一日多笔买卖的情形。 同一日既有卖也有买不现实,多笔买入只能在 csv 上合并记录,由此可能引起份额计算 0.01 的误差。可以处理分红日买入卖出的情形。 分级份额折算日封闭无法买入,所以程序直接忽略当天的买卖。因此不会出现多个操作共存的情形。 """ # the design on data remtable is disaster, it is very dangerous though works now code = self.aim.code if len(self.cftable) == 0: if len(self.status[self.status[code] != 0]) == 0: raise Exception("no other info to be add into cashflow table") i = 0 while self.status.iloc[i].loc[code] == 0: i += 1 value = self.status.iloc[i].loc[code] date = self.status.iloc[i].date if value > 0: rdate, cash, share = self.aim.shengou(value, date) rem = rm.buy([], share, rdate) else: raise TradeBehaviorError("You cannot sell first when you never buy") elif len(self.cftable) > 0: recorddate = list(self.status.date) lastdate = self.cftable.iloc[-1].date + pd.Timedelta(1, unit="d") while (lastdate not in self.aim.specialdate) and ( (lastdate not in recorddate) or ( (lastdate in recorddate) and ( self.status[self.status["date"] == lastdate].loc[:, code].any() == 0 ) ) ): lastdate += pd.Timedelta(1, unit="d") if (lastdate - yesterdayobj()).days >= 1: raise Exception("no other info to be add into cashflow table") date = lastdate label = self.aim.dividend_label # 现金分红 0, 红利再投 1 cash = 0 share = 0 rem = self.remtable.iloc[-1].rem rdate = date if (date in recorddate) and (date not in self.aim.zhesuandate): # deal with buy and sell and label the fenhongzaitouru, namely one label a 0.05 in the original table to label fenhongzaitouru value = self.status[self.status["date"] == date].iloc[0].loc[code] fenhongmark = round(10 * value - int(10 * value), 1) if fenhongmark == 0.5 and label == 0: label = 1 # fenhong reinvest value = round(value, 1) elif fenhongmark == 0.5 and label == 1: label = 0 value = round(value, 1) if value > 0: # value stands for purchase money rdate, dcash, dshare = self.aim.shengou(value, date) rem = rm.buy(rem, dshare, rdate) elif value < -0.005: # value stands for redemp share rdate, dcash, dshare = self.aim.shuhui( -value, date, self.remtable.iloc[-1].rem ) _, rem = rm.sell(rem, -dshare, rdate) elif value >= -0.005 and value < 0: # value now stands for the ratio to be sold in terms of remain positions, -0.005 stand for sell 100% remainshare = sum(self.cftable.loc[:, "share"]) ratio = -value / 0.005 rdate, dcash, dshare = self.aim.shuhui( remainshare * ratio, date, self.remtable.iloc[-1].rem ) _, rem = rm.sell(rem, -dshare, rdate) else: # in case value=0, when specialday is in record day rdate, dcash, dshare = date, 0, 0 cash += dcash share += dshare if date in self.aim.specialdate: # deal with fenhong and xiazhe comment = ( self.aim.price[self.aim.price["date"] == date] .iloc[0] .loc["comment"] ) if isinstance(comment, float): if comment < 0: dcash2, dshare2 = ( 0, sum([myround(sh * (-comment - 1)) for _, sh in rem]), ) # xiazhe are seperately carried out based on different purchase date rem = rm.trans(rem, -comment, date) # myround(sum(cftable.loc[:,'share'])*(-comment-1)) elif comment > 0 and label == 0: dcash2, dshare2 = ( myround(sum(self.cftable.loc[:, "share"]) * comment), 0, ) rem = rm.copy(rem) elif comment > 0 and label == 1: dcash2, dshare2 = ( 0, myround( sum(self.cftable.loc[:, "share"]) * ( comment / self.aim.price[self.aim.price["date"] == date] .iloc[0] .netvalue ) ), ) rem = rm.buy(rem, dshare2, date) cash += dcash2 share += dshare2 else: raise ParserFailure("comments not recoginized") self.cftable = self.cftable.append( pd.DataFrame([[rdate, cash, share]], columns=["date", "cash", "share"]), ignore_index=True, ) self.remtable = self.remtable.append( pd.DataFrame([[rdate, rem]], columns=["date", "rem"]), ignore_index=True )
def _addrow(self): """ Return cashflow table with one more line or raise an exception if there is no more line to add The same logic also applies to rem table 关于对于一个基金多个操作存在于同一交易日的说明:无法处理历史买入第一笔同时是分红日的情形, 事实上也不存在这种情形。无法处理一日多笔买卖的情形。 同一日既有卖也有买不现实,多笔买入只能在 csv 上合并记录,由此可能引起份额计算 0.01 的误差。可以处理分红日买入卖出的情形。 分级份额折算日封闭无法买入,所以程序直接忽略当天的买卖。因此不会出现多个操作共存的情形。 """ # the design on data remtable is disaster, it is very dangerous though works now # possibly failing cases include: # 买卖日记录是节假日,而顺延的日期恰好是折算日(理论上无法申赎)或分红日(可能由于 date 和 rdate 的错位而没有考虑到), # 又比如周日申购记录,周一申购记录,那么周日记录会现金流记在周一,继续现金流标更新将从周二开始,周一数据被丢弃 code = self.aim.code if len(self.cftable) == 0: if len(self.status[self.status[code] != 0]) == 0: raise Exception("no other info to be add into cashflow table") i = 0 while self.status.iloc[i].loc[code] == 0: i += 1 value = self.status.iloc[i].loc[code] date = self.status.iloc[i].date self.lastdate = date if len(self.price[self.price["date"] >= date]) > 0: date = self.price[self.price["date"] >= date].iloc[0]["date"] else: date = self.price[self.price["date"] <= date].iloc[-1]["date"] # 这里没有像下边部分一样仔细处理单独的 lastdate,hopefully 不会出现其他奇怪的问题,有 case 再说 # https://github.com/refraction-ray/xalpha/issues/47 # 凭直觉这个地方的处理很可能还有其他 issue if value > 0: feelabel = 100 * value - int(100 * value) if round(feelabel, 1) == 0.5: # binary encoding, 10000.005 is actually 10000.0050...1, see issue #59 feelabel = feelabel - 0.5 if abs(feelabel) < 1e-4: feelabel = 0 else: feelabel *= 100 else: feelabel = None value = int(value * 100) / 100 assert feelabel is None or feelabel >= 0.0, "自定义申购费必须为正值" rdate, cash, share = self.aim.shengou(value, date, fee=feelabel) rem = rm.buy([], share, rdate) else: raise TradeBehaviorError( "You cannot sell first when you never buy") elif len(self.cftable) > 0: # recorddate = list(self.status.date) if not getattr(self, "lastdate", None): lastdate = self.cftable.iloc[-1].date + pd.Timedelta(1, unit="d") else: lastdate = self.lastdate + pd.Timedelta(1, unit="d") while (lastdate not in self.aim.specialdate) and ( (lastdate not in self.recorddate_set) or ((lastdate in self.recorddate_set) and (self.status[self.status["date"] == lastdate].loc[:, code].any() == 0))): lastdate += pd.Timedelta(1, unit="d") if (lastdate - yesterdayobj()).days >= 1: raise Exception( "no other info to be add into cashflow table") if (lastdate - yesterdayobj()).days >= 1: raise Exception("no other info to be add into cashflow table") date = lastdate # 无净值日优先后移,无法后移则前移 # 还是建议日期记录准确,不然可能有无法完美兼容的错误出现 if len(self.price[self.price["date"] >= date]) > 0: date = self.price[self.price["date"] >= date].iloc[0]["date"] else: date = self.price[self.price["date"] <= date].iloc[-1]["date"] if date != lastdate and date in list(self.status.date): # 日期平移到了其他记录日,很可能出现问题! logger.warning( "账单日期 %s 非 %s 的净值记录日期,日期智能平移后 %s 与账单其他日期重合!交易处理极可能出现问题!! " "靠后日期的记录被覆盖" % (lastdate, self.code, date)) self.lastdate = lastdate if date > lastdate: self.lastdate = date # see https://github.com/refraction-ray/xalpha/issues/27, begin new date from last one in df is not reliable label = self.aim.dividend_label # 现金分红 0, 红利再投 1 cash = 0 share = 0 rem = self.remtable.iloc[-1].rem rdate = date if (lastdate in self.recorddate_set) and (date not in self.aim.zhesuandate): # deal with buy and sell and label the fenhongzaitouru, namely one label a 0.05 in the original table to label fenhongzaitouru value = self.status[ self.status["date"] <= lastdate].iloc[-1].loc[code] if date in self.aim.fenhongdate: # 0.05 的分红行为标记,只有分红日才有效 fenhongmark = round(10 * value - int(10 * value), 1) if fenhongmark == 0.5 and label == 0: label = 1 # fenhong reinvest value = value - math.copysign(0.05, value) elif fenhongmark == 0.5 and label == 1: label = 0 value = value - math.copysign(0.05, value) if value > 0: # value stands for purchase money feelabel = 100 * value - int(100 * value) if int(10 * feelabel) == 5: feelabel = (feelabel - 0.5) * 100 else: feelabel = None value = int(value * 100) / 100 rdate, dcash, dshare = self.aim.shengou( value, date, fee=feelabel ) # shengou fee is in the unit of percent, different than shuhui case rem = rm.buy(rem, dshare, rdate) elif value < -0.005: # value stands for redemp share feelabel = int(100 * value) - 100 * value if int(10 * feelabel) == 5: feelabel = feelabel - 0.5 else: feelabel = None value = int(value * 100) / 100 rdate, dcash, dshare = self.aim.shuhui( -value, date, self.remtable.iloc[-1].rem, fee=feelabel) _, rem = rm.sell(rem, -dshare, rdate) elif value >= -0.005 and value < 0: # value now stands for the ratio to be sold in terms of remain positions, -0.005 stand for sell 100% remainshare = sum(self.cftable[ self.cftable["date"] <= date].loc[:, "share"]) ratio = -value / 0.005 rdate, dcash, dshare = self.aim.shuhui( remainshare * ratio, date, self.remtable.iloc[-1].rem, 0) _, rem = rm.sell(rem, -dshare, rdate) else: # in case value=0, when specialday is in record day rdate, dcash, dshare = date, 0, 0 cash += dcash share += dshare if date in self.aim.specialdate: # deal with fenhong and xiazhe comment = self.price[self.price["date"] == date].iloc[0].loc["comment"] if isinstance(comment, float): if comment < 0: dcash2, dshare2 = ( 0, sum([ myround(sh * (-comment - 1)) for _, sh in rem ]), ) # xiazhe are seperately carried out based on different purchase date rem = rm.trans(rem, -comment, date) # myround(sum(cftable.loc[:,'share'])*(-comment-1)) elif comment > 0 and label == 0: dcash2, dshare2 = ( myround( sum(self.cftable.loc[:, "share"]) * comment), 0, ) rem = rm.copy(rem) elif comment > 0 and label == 1: dcash2, dshare2 = ( 0, myround( sum(self.cftable.loc[:, "share"]) * (comment / self.price[self.price["date"] == date].iloc[0].netvalue)), ) rem = rm.buy(rem, dshare2, date) cash += dcash2 share += dshare2 else: raise ParserFailure("comments not recognized") self.cftable = self.cftable.append( pd.DataFrame([[rdate, cash, share]], columns=["date", "cash", "share"]), ignore_index=True, ) self.remtable = self.remtable.append(pd.DataFrame( [[rdate, rem]], columns=["date", "rem"]), ignore_index=True)
def update(self): """ function to incrementally update the pricetable after fetch the old one """ lastdate = self.price.iloc[-1].date startvalue = self.price.iloc[-1].totvalue diffdays = (yesterdayobj() - lastdate).days if diffdays == 0: return None self._updateurl = ( "http://fund.eastmoney.com/f10/F10DataApi.aspx?type=lsjz&code=" + self.code + "&page=1&per=1") con = rget(self._updateurl) soup = BeautifulSoup(con.text, "lxml") items = soup.findAll("td") if dt.datetime.strptime(str(items[0].string), "%Y-%m-%d") == today(): diffdays += 1 if diffdays <= 10: # caution: there may be today data!! then a day gap will be in table self._updateurl = ( "http://fund.eastmoney.com/f10/F10DataApi.aspx?type=lsjz&code=" + self.code + "&page=1&per=" + str(diffdays)) con = rget(self._updateurl) soup = BeautifulSoup(con.text, "lxml") items = soup.findAll("td") elif ( diffdays > 10 ): ## there is a 20 item per page limit in the API, so to be safe, we query each page by 10 items only items = [] for pg in range(1, int(diffdays / 10) + 2): self._updateurl = ( "http://fund.eastmoney.com/f10/F10DataApi.aspx?type=lsjz&code=" + self.code + "&page=" + str(pg) + "&per=10") con = rget(self._updateurl) soup = BeautifulSoup(con.text, "lxml") items.extend(soup.findAll("td")) else: raise TradeBehaviorError( "Weird incremental update: the saved copy has future records") date = [] earnrate = [] comment = [] for i in range(int(len(items) / 6)): ts = pd.Timestamp(str(items[6 * i].string)) if (ts - lastdate).days > 0: date.append(ts) earnrate.append(float(items[6 * i + 1].string) * 1e-4) comment.append(_nfloat(items[6 * i + 5].string)) date = date[::-1] earnrate = earnrate[::-1] comment = comment[::-1] netvalue = [startvalue] for earn in earnrate: netvalue.append(netvalue[-1] * (1 + earn)) netvalue.remove(startvalue) df = pd.DataFrame({ "date": date, "netvalue": netvalue, "totvalue": netvalue, "comment": comment, }) df = df[df["date"].isin(opendate)] df = df.reset_index(drop=True) df = df[df["date"] <= yesterdayobj()] if len(df) != 0: self.price = self.price.append(df, ignore_index=True, sort=True) return df