def calculate_xirr(fund_pd, tx_pd): xirr_total = [] xirr_eq = [] xirr_db = [] xirr_hy = [] for fund in fund_pd.columns: x = fund_pd[fund] f_data = tx_pd[tx_pd['AMC_Scheme_Name'] == fund] xirr_data = f_data[['Transaction_Date', 'Amount(Credits/Debits)']].values.tolist() xirr_data.append([x['NAV date'], x['Current Value']]) if x['Fund Type'] == 'Equity': xirr_eq.extend(xirr_data) elif x['Fund Type'] == 'Debt': xirr_db.extend(xirr_data) elif x['Fund Type'] == 'Hybrid': xirr_hy.extend(xirr_data) xirr_total.extend(xirr_data) x['XIRR'] = xirr(xirr_data) * 100 fund_pd['Total'] = None fund_pd['Total']['XIRR'] = xirr(xirr_total) * 100 if len(xirr_eq) > 0: fund_pd['Equity'] = None fund_pd['Equity']['XIRR'] = xirr(xirr_eq) * 100 if len(xirr_db) > 0: fund_pd['Debt'] = None fund_pd['Debt']['XIRR'] = xirr(xirr_db) * 100 if len(xirr_hy) > 0: fund_pd['Hybrid'] = None fund_pd['Hybrid']['XIRR'] = xirr(xirr_hy) * 100
def main(argv=None): if argv: cashflows.append(' '.join(argv)) else: read_flows() if not parse_flows(): return 1 print('\n------ Cashflow ------') fmt = '%s %0.2f' if any(not f == int(f) for d, f in cashflows) else '%s %d' for d, f in cashflows: print(fmt % (d, f)) print('----------------------') print('xIRR: %0.2f%%' % (100 * xirr(cashflows), )) return 0
def xirrCashflow(self, df_, df_trans): """This function will calculate the xirr of a cashflow with the following transactions: Dataframe: date (index) isin numstocks """ logger = logging.getLogger(__name__) ret = None try: msg = f"Starting calculate xirr of cashflow" logger.debug(msg) self.gc.writeJobStatus("Running", statusMessage=msg) # convert transactions to tupel-list l = {} # loop through transactions and get stock value for i, row in df_trans.iterrows(): # get stock value for isin at time x sv = self.getStockVal(i, row['isin'], 'close') v = sv * row['numstocks'] if i in l: l[i] = l[i] + v else: l[i] = v return xirr.xirr(l) self.gc.writeJobStatus("Running", statusMessage=msg + " - DONE") logger.debug(msg + " - DONE") except Exception as e: logger.exception('Crash!', exc_info=e) self.gc.numErrors += 1 self.gc.errMsg += "Crash xirrCashflow; " return ret
def fund_summary(i): tab = [] head = [ "Fund Name", "NAV date", "Total Units", "Total Inv", "Cur NAV", "Current Value", "P/L", "XIRR", "Duration", "Frequency" ] f = i.replace("Franklin_Templeton_Franklin", "Franklin_Templeton") f = f.replace("Aditya_Birla_Sun_Life", "ABSL") f = f.replace("Franklin_Templeton_India", "Franklin") #print(i, txn_summary.keys()) tab.append([ f, cur_data[i]["date"], txn_summary[i]["tot_units"], txn_summary[i]["tot_inv"], cur_data[i]["nav"], txn_summary[i]["tot_val"], round(txn_summary[i]["tot_val"] - txn_summary[i]["tot_inv"], 4), round(100 * xirr(txn_summary[i]["xirr"]), 2), txn_summary[i]["duration"], txn_summary[i]["frequency"] ]) print( tabulate(tab, headers=head, numalign="left", tablefmt="grid", floatfmt='.8g'))
def create_return_xirr(cashflows, method=['twrr', 'mwrr']): """ Метод расчитывает доходность портфеля с учетом пополнений и изъятий денежных средств портфеля. Расчет производится на основе DataFrame с ежедневными данными о стоимости портфеля и денежного потока на дату. Для расчета используется два метода: - взвешенный по деньгам (money-weighted return) - взвешенный по времени (time-weighted return) :param cashflows: pandas DataFrame с столбцами ['total', 'cashflow'], имя столбца не важно, важна последовательность. index - дата в формает timestamp или datetime64, в которую был произведегн денежный поток и определена стоимость портфеля total - стоимость портфеля на соответствующую дату в формате float cashflow - размер денежного потока на соответствующую дату в формате float :param method: список методов для расчета доходности, определяет метод по которому будет считаться доходность: 'twrr' - time-weighted rate of return. Доходность, взвешенная по времени 'mwrr' - money-weighted rate of return. Долходность, взвешенная по деньгам может принимать значения: ['twrr'] ['twrr', 'mwrr'] ['mwrr'] :return: словарь {'twrr';{'return': float, 'data': DataFrame}, 'mwrr':{'return': float, 'data': DataFrame}} return - значение годовой доходности посчитанной соответствующим методом в формате float data - массив значений доходности в каждый момент времени """ def twrr(cf): """ Расчет взвешенной по времени доходности, при неравномерных на не равномерных периодах внесения и изъятия денег в формате DataFrame :param cashflows: pandas DataFrame с столбцами ['total', 'cashflow'], имя столбца не важно, важна последовательность. index - дата в формает timestamp или datetime64, в которую был произведегн денежный поток и определена стоимость портфеля total - стоимость портфеля на соответствующую дату в формате float cashflow - размер денежного потока на соответствующую дату в формате float каждая строка равна дневным значениям. :return: [twrr, annual return, revenue] *twrr - взвешенная по времени среднегодовая доходность за весь период. формат: float *annual return - годовая доходность на каждый день *total_return - доходность накопительным итогом за весь период * *значения взвешенной по времени доходности на каждый период в входном датафрейме """ start_date = cf.index[0] cf = cf.copy() # Формируем периоды расчета изменения стоимости портфеля между пополнениями / изъятиями # Формируем столбец с накопительным количеством строк с не нулевым cashflow и смещаем на один, что бы в интервал # попадала строка с следующим casflow для расчета общей стоимоти до изменения cf.loc[:, 'interval'] = np.cumsum(cf[cf.columns[1]] != 0).shift(1) # Формируем столбец с расчетом изменения общей стоимости портфеля за каждый период по сравнению # с стоимостью в предыдущим периодом убирая из расчета cashflow за текущий период. cf['change'] = ((cf['total'] + cf['cashflow']) / cf['total'].shift(periods=1)).fillna(1) # Для каждой строки считаем накопительное произведение изменение общей стоимости портфеля в рамках периода cf['prod_interval'] = cf.groupby('interval').change.cumprod().fillna(1) # Готовим вспомогательный столбец для хранения полного изменения по каждому периоду cf['prod_previous_period'] = cf['prod_interval'][cf['cashflow'] != 0] cf['prod_previous_period'].fillna(1, inplace=True) # Расчитываем накопленную доходность умножая изменение за текущий период на общие изменения за прошлые периоды cf['revenue'] = cf.prod_previous_period.cumprod() # Рассчитываем годовую доходность, умножая на приведенный к году текущий срок с даты старта портфеля cf['revenue'][cf['cashflow'] == 0] = cf.revenue * cf.prod_interval cf['annual return'] = cf.revenue**(365. / (cf.index - start_date).days) - 1 return [cf['annual return'][-1], cf['annual return']] result = {} if 'twrr' in method: twrr, data = twrr(cashflows) result['twrr'] = twrr cashflows['twrr'] = data if 'mwrr' in method: #переводим DataFrame в numpy для скорости выполнения оптимизации cf_np = cashflows[cashflows.columns[1]].reset_index().to_numpy() dict_res = [] # для каждого значения стоимости портфеля, считаем mwrr for i in range(1, len(cf_np)): arr_cf = cf_np[:i + 1].copy() #прибавляем положительный итоговый денежный поток на дату, равный стоимости портфеля arr_cf[i, 1] += cashflows[cashflows.columns[0]][i] dict_t = {k: v for k, v in arr_cf[np.abs(arr_cf[:, 1]) > 1e-10]} dict_res += [xirr.xirr(dict_t)] cashflows.loc[:, 'mwrr'] = [0] + dict_res result['mwrr'] = cashflows['mwrr'][-1] result['data'] = cashflows return result
def calcXIRR(gc, df_full, myDepot, days, valColumn='Value-total', investColumn='Invested-total', save=True): """calculates the XIRR of the depot for the last x days""" loc = locals() logger = logging.getLogger(__name__) try: msg = f"Starting calcXIRR with {loc}" logger.debug(msg) gc.writeJobStatus("Running", statusMessage=msg) # get value at start date startDate = datetime.datetime.now() - datetime.timedelta(days=days) df = df_full.loc[(df_full.index >= startDate)] df = df[[valColumn, investColumn]] l_xirr = {} startVal = 0 if (len(df.index) > 0): startDate = df.index[0] startVal = df.iloc[0][valColumn] l_xirr[startDate] = startVal # get transactions df_diff = df.diff().dropna() df_diff = df_diff[df_diff[investColumn] != 0] for i, row in df_diff.iterrows(): l_xirr[i] = row[investColumn] # get value at end l_xirr[df.index[-1]] = -df.iloc[-1][valColumn] x = -1 if ((df.index[-1] - startDate).days > 30): x = xirr.xirr(l_xirr) logger.debug(f"XIRR of depot {myDepot}: {x}") # Save now = pd.Timestamp(datetime.datetime.now(UTC)).replace( hour=0, minute=0, second=0, microsecond=0) df_res = pd.DataFrame(index=[now], columns=["KPI-Value", "KPI", "Depot"], data=[[x, f"XIRR-{days}", myDepot]]) #print(df_res) if ((x != float("inf")) and (x != float("-inf")) and (save == True)): gc.influx_write_api.write( gc.influx_db, gc.influx_org, record=df_res, data_frame_measurement_name="KPIs", data_frame_tag_columns=['Depot', 'KPI']) gc.influx_write_api.close() else: logger.warn( f"Not calculating xirr due to short timespan of {(df.index[-1] - startDate).days} days" ) gc.writeJobStatus("Running", statusMessage=msg + " - DONE") logger.debug(msg + " - DONE") if (x == float("inf")): x = 9999 if (x == float("-inf")): x = -9999 return x except Exception as e: logger.exception(f'Crash calcXIRR with {loc}!', exc_info=e) gc.numErrors += 1 gc.errMsg += f"Crash calcXIRR with {loc}; "
def print_summary(write_to_file): tab = [] head = [ "Fund Name", "NAV date", "Total Units", "Total Inv", "Cur NAV", "Current Value", "P/L", "XIRR", "Day P/L", "Duration", "Frequency" ] tot_inv = 0 tot_val = 0 tot_xirr = [] cat_xirr = [] cat_inv = 0 cat_tot = 0 fund_hist = [0, 0, 0] cat_hist = [0, 0, 0] summary = {} nav_date = [] eq_total = 0 hy_total = 0 db_total = 0 inv_total = 0 for f in get_funds("Equity"): if f in txn_summary.keys(): eq_total += txn_summary[f]["tot_val"] for f in get_funds("Hybrid"): if f in txn_summary.keys(): hy_total += txn_summary[f]["tot_val"] for f in get_funds("Debt"): if f in txn_summary.keys(): db_total += txn_summary[f]["tot_val"] inv_total = eq_total + hy_total + db_total #print("E", eq_total, "H", hy_total, "D", db_total, "T", inv_total); for i in txn_summary.keys(): summary[i] = [ shorten_fund(i), cur_data[i]["date"], txn_summary[i]["tot_units"], txn_summary[i]["tot_inv"], cur_data[i]["nav"], get_wt(i, txn_summary[i]["tot_val"], eq_total, hy_total, db_total), round(txn_summary[i]["tot_val"] - txn_summary[i]["tot_inv"], 4), str(round(100 * xirr(txn_summary[i]["xirr"]), 2)) + "%", pl_pct(txn_summary[i]["day_chg"], txn_summary[i]["tot_val"]), txn_summary[i]["duration"], txn_summary[i]["frequency"] ] tot_xirr.extend(txn_summary[i]["xirr"]) for t in "Equity", "Debt", "Hybrid": cat_inv = 0 cat_val = 0 cat_xirr = [] cat_hist = [0, 0, 0] #tab.append([t]) for i in get_funds(t): if i not in txn_summary.keys(): continue if t == "Equity" or t == "Hybrid": nav_date.append(cur_data[i]["date"]) tab.append(summary[i]) tot_inv += txn_summary[i]["tot_inv"] tot_val += txn_summary[i]["tot_val"] cat_inv += txn_summary[i]["tot_inv"] cat_val += txn_summary[i]["tot_val"] cat_hist[0] += txn_summary[i]["day_chg"] cat_hist[1] += txn_summary[i]["week_chg"] cat_hist[2] += txn_summary[i]["month_chg"] cat_xirr.extend(txn_summary[i]["xirr"]) #print(cat_xirr) tab.append([ t + " ========>", "========", "========", round(cat_inv, 4), "========", str(round(cat_val, 2)) + " (" + str(round((cat_val / inv_total) * 100, 1)) + "%)", round(cat_val - cat_inv, 4), str(round(100 * xirr(cat_xirr), 2)) + "%", "D: " + pl_pct(cat_hist[0], cat_val), "W: " + pl_pct(cat_hist[1], cat_val), "M: " + pl_pct(cat_hist[2], cat_val) ]) fund_hist[0] += cat_hist[0] fund_hist[1] += cat_hist[1] fund_hist[2] += cat_hist[2] # pdb.set_trace() #tab.append(["Total", cur_data[i]["date"], "", tot_inv, "", tot_val, tot_val - tot_inv, round(100 * xirr(tot_xirr), 2), round(fund_hist[0], 2), round(fund_hist[1], 2)]) tab.append([ "Total" + " ========>", cur_data[i]["date"], "", round(tot_inv, 2), "", round(tot_val, 2), round(tot_val - tot_inv, 2), str(round(100 * xirr(tot_xirr), 2)) + "%", "D: " + pl_pct(fund_hist[0], tot_val), "W: " + pl_pct(fund_hist[1], tot_val), "M: " + pl_pct(fund_hist[2], tot_val) ]) print( tabulate(tab, headers=head, numalign="left", tablefmt="grid", floatfmt='.8g')) file_name = max(set(nav_date), key=nav_date.count) #print(file_name, nav_date) if write_to_file: report_name = os.path.abspath( os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir)) + report_dir + time.strftime( file_name, time.localtime()) with open(report_name, "w") as r: r.write( tabulate(tab, headers=head, numalign="left", tablefmt="grid", floatfmt='.8g')) r.write("\n")