def sell(remc, share, date): ''' :returns: tuple, (sold rem, new rem) sold rem is the positions being sold while new rem is the positions being held ''' rem = copy(remc) share = myround(share) date = convert_date(date) totposition = sum([pos[1] for pos in rem]) # the remaining shares if totposition == 0: return ([], []) if (date - rem[-1][0]).days < 0: raise Exception(_errmsg) if share > totposition: share = totposition # not raise error when you sell more than you buy soldrem = [] newrem = [] for i, pos in enumerate(rem): if share > myround(sum([rem[j][1] for j in range(i + 1)])): soldrem.append(rem[i]) elif share == myround(sum([rem[j][1] for j in range(i + 1)])): soldrem.append(rem[i]) elif share < myround(sum([rem[j][1] for j in range(i + 1)])): if share > sum([rem[j][1] for j in range(i)]): soldrem.append([rem[i][0], share - sum([rem[j][1] for j in range(i)])]) newrem.append([rem[i][0], sum([rem[j][1] for j in range(i + 1)]) - share]) elif share <= sum([rem[j][1] for j in range(i)]): newrem.append(rem[i]) return (soldrem, newrem)
def status_gen(self, date): # 过滤交易日这一需求,交给各个类自由裁量,这里网格类就需要过掉非交易日干扰, # 而定投类中则不过掉,遇到非交易日顺延定投更合理些 if date.strftime("%Y-%m-%d") not in opendate: return 0 if date == self.start: if self.buypercent[0] == 0: self.pos += 1 return myround(self.totmoney / self.division) else: return 0 value = self.price[self.price["date"] <= date].iloc[-1].loc["netvalue"] valueb = self.price[ self.price["date"] <= date].iloc[-2].loc["netvalue"] action = 0 for i, buypt in enumerate(self.buypts): if (value - buypt) <= 0 and (valueb - buypt) > 0 and self.pos <= i: self.pos += 1 action += myround(self.totmoney / self.division) for j, sellpt in enumerate(self.sellpts): if (value - sellpt) >= 0 and (valueb - sellpt) < 0 and self.pos > j: action += -1 / self.pos self.pos += -1 return action
def dailyreport(self, date=yesterdayobj()): date = convert_date(date) partcftb = self.cftable[self.cftable["date"] <= date] value = self.get_netvalue(date) if len(partcftb) == 0: reportdict = { "基金名称": [self.name], "基金代码": [self.code], "当日净值": [value], "持有份额": [0], "基金现值": [0], "基金总申购": [0], "历史最大占用": [0], "基金分红与赎回": [0], "基金收益总额": [0], } df = pd.DataFrame(reportdict, columns=reportdict.keys()) return df # totinput = myround(-sum(partcftb.loc[:,'cash'])) totinput = myround(-sum( [row["cash"] for _, row in partcftb.iterrows() if row["cash"] < 0])) totoutput = myround( sum([ row["cash"] for _, row in partcftb.iterrows() if row["cash"] > 0 ])) currentshare = myround(sum(partcftb.loc[:, "share"])) currentcash = myround(currentshare * value) btnk = bottleneck(partcftb) turnover = turnoverrate(partcftb, date) ereturn = myround(currentcash + totoutput - totinput) if currentshare == 0: unitcost = 0 else: unitcost = round((totinput - totoutput) / currentshare, 4) if btnk == 0: returnrate = 0 else: returnrate = round((ereturn / btnk) * 100, 4) reportdict = { "基金名称": [self.name], "基金代码": [self.code], "当日净值": [value], "单位成本": [unitcost], "持有份额": [currentshare], "基金现值": [currentcash], "基金总申购": [totinput], "历史最大占用": [btnk], "基金持有成本": [totinput - totoutput], "基金分红与赎回": [totoutput], "换手率": [turnover], "基金收益总额": [ereturn], "投资收益率": [returnrate], } df = pd.DataFrame(reportdict, columns=reportdict.keys()) return df
def status_gen(self, date): if date.strftime("%Y-%m-%d") not in opendate: return 0 rows = self.price[self.price["date"] <= date] if len(rows) == 1: return 0 value = rows.iloc[-1].loc[self.col] valueb = rows.iloc[-2].loc[self.col] action = 0 if self.buylow is True: judge = 1 else: judge = -1 for i, term in enumerate(self.buy): if (judge * (value - term[0]) <= 0 < judge * (valueb - term[0]) and self.pos + sum([it[1] for it in self.buy[i:]]) <= 1): self.pos += term[1] action += myround(self.totmoney * term[1]) self.selllevel = 0 if self.sell is not None: for i, term in enumerate(self.sell): if (judge * (value - term[0]) >= 0 > judge * (valueb - term[0]) and self.pos > 0 and self.selllevel <= i): deltaaction = myround(term[1] / sum([it[1] for it in self.sell[i:]])) action -= (1 + action) * deltaaction # 需考虑一日卖出多仓的情形 self.pos = (1 - deltaaction) * self.pos self.selllevel = i + 1 return action
def shuhui(self, share, date, rem): """ give the cashout considering redemption rates as zero. if the date is not a trade date, then the purchase would happen on the next trade day, if the date is in the furture, then the trade date is taken as yesterday. :param share: float or int, number of shares to be sold :param date: string or object of date :returns: three elements tuple, the first is dateobj the second is a positive float for cashout, the third is a negative float for share decrease """ date = convert_date(date) tots = sum([remitem[1] for remitem in rem if remitem[0] <= date]) if share > tots: sh = tots else: sh = share partprice = self.price[self.price["date"] >= date] if len(partprice) == 0: row = self.price[self.price["date"] < date].iloc[-1] else: row = partprice.iloc[0] value = myround(sh * row.netvalue) return (row.date, value, -myround(sh))
def _shengoucal(sg, sgf, value, label): """ Infer the share of buying fund by money input, the rate of fee in the unit of %, and netvalue of fund :param sg: positive float, 申购金额 :param sgf: positive float, 申购费,以%为单位,如 0.15 表示 0.15% :param value: positive float, 对应产品的单位净值 :param label: integer, 1 代表份额正常进行四舍五入, 2 代表份额直接舍去小数点两位之后。金额部分都是四舍五入 :returns: tuple of two positive float, 净申购金额和申购份额 """ jsg = myround(sg / (1 + sgf * 1e-2)) share = myround(jsg / value, label) return (jsg, share)
def _shuhui_by_share(self, share, date, rem): date = convert_date(date) tots = sum([remitem[1] for remitem in rem if remitem[0] <= date]) if share > tots: sh = tots else: sh = share partprice = self.price[self.price["date"] >= date] if len(partprice) == 0: row = self.price[self.price["date"] < date].iloc[-1] else: row = partprice.iloc[0] value = myround(sh * row.netvalue) return ( row.date, value, -myround(sh), ) # TODO: 这里 myround 是否也和 round_label 有关,有待考证
def bottleneck(cftable): """ find the max total input in the history given cftable with cash column :param cftable: pd.DataFrame of cftable """ if len(cftable) == 0: return 0 # cftable = cftable.reset_index(drop=True) # unnecessary as iloc use natural rows instead of default index inputl = [-sum(cftable.iloc[:i].cash) for i in range(1, len(cftable) + 1)] return myround(max(inputl))
def shuhui(self, share, date, rem): """ give the cashout based on rem term considering redemption rates :returns: three elements tuple, the first is dateobj the second is a positive float for cashout, the third is a negative float for share decrease """ # value = myround(share*self.price[self.price['date']==date].iloc[0].netvalue) date = convert_date(date) partprice = self.price[self.price["date"] >= date] if len(partprice) == 0: row = self.price[self.price["date"] < date].iloc[-1] else: row = partprice.iloc[0] soldrem, _ = rm.sell(rem, share, row.date) value = 0 sh = myround(sum([item[1] for item in soldrem])) for d, s in soldrem: value += myround(s * row.netvalue * (1 - self.feedecision( (row.date - d).days) * 1e-2)) return (row.date, value, -sh)
def briefdailyreport(self, date=yesterdayobj()): """ quick summary of highly used attrs for trade :param date: string or object of datetime :returns: dict with several attrs: date, unitvalue, currentshare, currentvalue """ date = convert_date(date) partcftb = self.cftable[self.cftable["date"] <= date] if len(partcftb) == 0: return {} unitvalue = self.get_netvalue(date) currentshare = myround(sum(partcftb.loc[:, "share"])) currentvalue = myround(currentshare * unitvalue) return { "date": date, "unitvalue": unitvalue, "currentshare": currentshare, "currentvalue": currentvalue, }
def briefdailyreport(self, date=yesterdayobj()): ''' quick summary of highly used attrs for trade :param date: string or object of datetime :returns: dict with several attrs: date, unitvalue, currentshare, currentvalue ''' date = convert_date(date) partcftb = self.cftable[self.cftable['date'] <= date] if len(partcftb) == 0: return {} unitvalue = self.aim.price[ self.aim.price['date'] <= date].iloc[-1].netvalue currentshare = myround(sum(partcftb.loc[:, 'share'])) currentvalue = myround(currentshare * unitvalue) return { 'date': date, 'unitvalue': unitvalue, 'currentshare': currentshare, 'currentvalue': currentvalue }
def _vcash(totmoney, totcftable, cashobj): ''' return a virtue status table with a mf(cash) column based on the given tot money and cftable ''' cashl = [] cashl.append(totmoney + totcftable.iloc[0].cash) for i in range(len(totcftable) - 1): date = totcftable.iloc[i + 1].date delta = totcftable.iloc[i + 1].cash if delta < 0: cashl.append(myround(delta / cashobj.price[cashobj.price['date'] <= date].iloc[-1].netvalue)) else: cashl.append(delta) datadict = {'date': totcftable.loc[:, 'date'], 'mf': cashl} return pd.DataFrame(data=datadict)
def shengou(self, value, date): """ give the realdate deltacash deltashare tuple based on purchase date and purchase amount if the date is not a trade date, then the purchase would happen on the next trade day, if the date is in the furture, then the trade date is taken as yesterday. :param value: the money for purchase :param date: string or object of date :returns: three elements tuple, the first is the actual dateobj of commit the second is a negative float for cashin, the third is a positive float for share increase """ row = self.price[self.price["date"] >= date].iloc[0] share = _shengoucal(value, self.rate, row.netvalue, label=self.label)[1] return (row.date, -myround(value), share)
def trans(remc, coef, date): ''' 在基金份额折算时,将之前持有的仓位按现值折算,相当于前复权 :param coef: the factor shown in comment column of fundinfo().price, but with positive value :param date: string in date form or datetime obj :returns: new rem after converting ''' rem = copy(remc) date = convert_date(date) if len(rem) == 0: return [] if (date - rem[-1][0]).days <= 0: raise Exception(_errmsg) newrem = [[item[0], myround(item[1] * coef)] for item in rem] return newrem
def unitcost(self, date=yesterdayobj()): """ give the unitcost of fund positions :param date: string or object of datetime :returns: float number of unitcost """ partcftb = self.cftable[self.cftable["date"] <= date] if len(partcftb) == 0: return 0 totnetinput = myround(-sum(partcftb.loc[:, "cash"])) currentshare = self.briefdailyreport(date).get("currentshare", 0) # totnetinput if currentshare > 0: unitcost = totnetinput / currentshare else: unitcost = 0 return unitcost
def buy(remc, share, date): ''' :param remc: array of two-elements arrays, eg [[pd.Timestamp(), 50],[pd.Timestamp(), 30] the first element in tuple is pandas.Timestamp object for date while the second element is positive float for remaining shares, tuples in rem MUST be time ordered. :param share: positive float, only 2 decimal is meaningful. :param date: string in the date form or datetime object :returns: new rem after the buying ''' rem = copy(remc) share = myround(share) date = convert_date(date) if len(rem) == 0: return [[date, share]] elif (date - rem[-1][0]).days > 0: rem.append([date, share]) return rem elif (date - rem[-1][0]).days == 0: rem[-1][1] = rem[-1][1] + share return rem else: raise Exception(_errmsg)
def dailyreport(self, date=yesterdayobj()): """ breif report dict of certain date status on the fund investment :param date: string or obj of date, show info of the date given :returns: dict of various data on the trade positions """ date = convert_date(date) partcftb = self.cftable[self.cftable["date"] <= date] print(partcftb) value = self.aim.price[ self.aim.price["date"] <= date].iloc[-1].netvalue if len(partcftb) == 0: reportdict = { "基金名称": [self.aim.name], "基金代码": [self.aim.code], "当日净值": [value], "持有份额": [0], "基金现值": [0], "基金总申购": [0], "历史最大占用": [0], "基金分红与赎回": [0], "基金收益总额": [0], } df = pd.DataFrame(reportdict, columns=reportdict.keys()) return df # totinput = myround(-sum(partcftb.loc[:,'cash'])) totinput = myround(-sum( [row["cash"] for _, row in partcftb.iterrows() if row["cash"] < 0])) totoutput = myround( sum([ row["cash"] for _, row in partcftb.iterrows() if row["cash"] > 0 ])) currentshare = myround(sum(partcftb.loc[:, "share"])) currentcash = myround(currentshare * value) btnk = bottleneck(partcftb) turnover = turnoverrate(partcftb, date) ereturn = myround(currentcash + totoutput - totinput) if currentshare == 0: unitcost = 0 else: unitcost = round((totinput - totoutput) / currentshare, 4) if btnk == 0: returnrate = 0 else: returnrate = round((ereturn / btnk) * 100, 4) reportdict = { "基金名称": [self.aim.name], "基金代码": [self.aim.code], "当日净值": [value], "单位成本": [unitcost], "持有份额": [currentshare], "基金现值": [currentcash], "基金总申购": [totinput], "历史最大占用": [btnk], "基金持有成本": [totinput - totoutput], "基金分红与赎回": [totoutput], "换手率": [turnover], "基金收益总额": [ereturn], "投资收益率": [returnrate], } df = pd.DataFrame(reportdict, columns=reportdict.keys()) return df
def dailyreport(self, date=yesterdayobj()): ''' breif report dict of certain date status on the fund investment :param date: string or obj of date, show info of the date given :returns: dict of various data on the trade positions ''' date = convert_date(date) partcftb = self.cftable[self.cftable['date'] <= date] value = self.aim.price[ self.aim.price['date'] <= date].iloc[-1].netvalue if len(partcftb) == 0: reportdict = { '基金名称': [self.aim.name], '基金代码': [self.aim.code], '当日净值': [value], '持有份额': [0], '基金现值': [0], '基金总申购': [0], '历史最大占用': [0], '基金分红与赎回': [0], '基金收益总额': [0] } df = pd.DataFrame(reportdict, columns=reportdict.keys()) return df # totinput = myround(-sum(partcftb.loc[:,'cash'])) totinput = myround(-sum( [row['cash'] for _, row in partcftb.iterrows() if row['cash'] < 0])) totoutput = myround( sum([ row['cash'] for _, row in partcftb.iterrows() if row['cash'] > 0 ])) currentshare = myround(sum(partcftb.loc[:, 'share'])) currentcash = myround(currentshare * value) btnk = bottleneck(partcftb) turnover = turnoverrate(partcftb, date) ereturn = myround(currentcash + totoutput - totinput) if currentshare == 0: unitcost = 0 else: unitcost = round((totinput - totoutput) / currentshare, 4) if btnk == 0: returnrate = 0 else: returnrate = round((ereturn / btnk) * 100, 4) reportdict = { '基金名称': [self.aim.name], '基金代码': [self.aim.code], '当日净值': [value], '单位成本': [unitcost], '持有份额': [currentshare], '基金现值': [currentcash], '基金总申购': [totinput], '历史最大占用': [btnk], '基金持有成本': [totinput - totoutput], '基金分红与赎回': [totoutput], '换手率': [turnover], '基金收益总额': [ereturn], '投资收益率': [returnrate] } df = pd.DataFrame(reportdict, columns=reportdict.keys()) return df
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)