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)