# -*- coding: utf-8 -*-
"""
module for mul and mulfix class: fund combination management
"""
import pandas as pd
from pyecharts import options as opts
from pyecharts.charts import Pie, ThemeRiver
from xalpha.cons import convert_date, myround, pie_opts, yesterdaydash, yesterdayobj
from xalpha.evaluate import evaluate
from xalpha.exceptions import FundTypeError, TradeBehaviorError
from xalpha.indicator import indicator
from xalpha.info import cashinfo, fundinfo, mfundinfo
from xalpha.trade import bottleneck, trade, turnoverrate, vtradevolume, xirrcal
[docs]class mul:
"""
multiple fund positions manage class
:param fundtradeobj: list of trade obj which you want to analyse together
:param status: the status table of trade, all code in this table would be considered.
one must provide one of the two paramters, if both are offered, status will be overlooked
:param fetch: boolean, when open the fetch option, info class will try fetching from local files first in the init
:param save: boolean, when open the save option, info classes automatically save the class to files
:param path: string, the file path prefix of IO, or object or engine from sqlalchemy to connect sql database
:param form: string, the format of IO, options including: 'csv','sql'
"""
def __init__(
self, *fundtradeobj, status=None, fetch=False, save=False, path="", form="csv"
):
if not fundtradeobj:
# warning: not a very good way to automatic generate these fund obj
# because there might be some funds use round_down for share calculation, ie, label=2 must be given
# unless you are sure corresponding funds are added to the droplist
fundtradeobj = []
for code in status.columns[1:]:
try:
fundtradeobj.append(
trade(
fundinfo(
code, fetch=fetch, save=save, path=path, form=form
),
status,
)
)
except FundTypeError:
fundtradeobj.append(
trade(
mfundinfo(
code, fetch=fetch, save=save, path=path, form=form
),
status,
)
)
self.fundtradeobj = tuple(fundtradeobj)
self.totcftable = self._mergecftb()
[docs] def tot(self, prop="基金现值", date=yesterdayobj()):
"""
sum of all the values from one prop of fund daily report,
of coures many of the props make no sense to sum
:param prop: string defined in the daily report dict,
typical one is 'currentvalue' or 'originalpurchase'
"""
res = 0
for fund in self.fundtradeobj:
res += fund.dailyreport().iloc[0][prop]
return res
[docs] def combsummary(self, date=yesterdayobj()):
"""
brief report table of every funds and the combination investment
:param date: string or obj of date, show info of the date given
:returns: empty dict if nothing is remaining that date
dict of various data on the trade positions
"""
date = convert_date(date)
columns = [
"基金名称",
"基金代码",
"当日净值",
"单位成本",
"持有份额",
"基金现值",
"基金总申购",
"历史最大占用",
"基金持有成本",
"基金分红与赎回",
"换手率",
"基金收益总额",
"投资收益率",
]
summarydf = pd.DataFrame([], columns=columns)
for fund in self.fundtradeobj:
summarydf = summarydf.append(
fund.dailyreport(date), ignore_index=True, sort=True
)
tname = "总计"
tcode = "total"
tunitvalue = float("NaN")
tunitcost = float("NaN")
tholdshare = float("NaN")
tcurrentvalue = summarydf["基金现值"].sum()
tpurchase = summarydf["基金总申购"].sum()
tbtnk = bottleneck(self.totcftable[self.totcftable["date"] <= date])
tcost = summarydf["基金持有成本"].sum()
toutput = summarydf["基金分红与赎回"].sum()
tturnover = turnoverrate(self.totcftable[self.totcftable["date"] <= date], date)
# 计算的是总系统作为整体和外界的换手率,而非系统各成分之间的换手率
tearn = summarydf["基金收益总额"].sum()
trate = round(tearn / tbtnk * 100, 4)
trow = pd.DataFrame(
[
[
tname,
tcode,
tunitvalue,
tunitcost,
tholdshare,
tcurrentvalue,
tpurchase,
tbtnk,
tcost,
toutput,
tturnover,
tearn,
trate,
]
],
columns=columns,
)
summarydf = summarydf.append(trow, ignore_index=True, sort=True)
return summarydf[columns].sort_values(by="基金现值", ascending=False)
def _mergecftb(self):
"""
merge the different cftable for different funds into one table
"""
dtlist = []
for fund in self.fundtradeobj:
dtlist2 = []
for _, row in fund.cftable.iterrows():
dtlist2.append((row["date"], row["cash"]))
dtlist.extend(dtlist2)
nndtlist = set([item[0] for item in dtlist])
nndtlist = sorted(list(nndtlist), key=lambda x: x)
reslist = []
for date in nndtlist:
reslist.append(sum([item[1] for item in dtlist if item[0] == date]))
df = pd.DataFrame(data={"date": nndtlist, "cash": reslist})
df = df[df["cash"] != 0]
df = df.reset_index(drop=True)
return df
[docs] def xirrrate(self, date=yesterdayobj(), guess=0.1):
"""
xirr rate evauation of the whole invest combination
"""
return xirrcal(self.totcftable, self.fundtradeobj, date, guess)
[docs] def evaluation(self, start=None):
"""
give the evaluation object to analysis funds properties themselves instead of trades
:returns: :class:`xalpha.evaluate.evaluate` object, with referenced funds the same as funds
we invested
"""
case = evaluate(
*[fundtrade.aim for fundtrade in self.fundtradeobj], start=start
)
return case
[docs] def v_positions(self, date=yesterdayobj(), vopts=None):
"""
pie chart visualization of positions ratio in combination
"""
sdata = sorted(
[
(fob.aim.name, fob.briefdailyreport(date).get("currentvalue", 0))
for fob in self.fundtradeobj
],
key=lambda x: x[1],
reverse=True,
)
pie = Pie()
if vopts is None:
vopts = pie_opts
pie.add(series_name="总值占比", data_pair=sdata)
pie.set_global_opts(**vopts)
return pie.render_notebook()
[docs] def v_positions_history(self, end=yesterdaydash(), **vkwds):
"""
river chart visulization of positions ratio history
use text size to avoid legend overlap in some sense, eg. legend_text_size=8
"""
start = self.totcftable.iloc[0].date
times = pd.date_range(start, end)
tdata = []
for date in times:
sdata = sorted(
[
(
date,
fob.briefdailyreport(date).get("currentvalue", 0),
fob.aim.name,
)
for fob in self.fundtradeobj
],
key=lambda x: x[1],
reverse=True,
)
tdata.extend(sdata)
tr = ThemeRiver()
tr.add(
series_name=[foj.aim.name for foj in self.fundtradeobj],
data=tdata,
label_opts=opts.LabelOpts(is_show=False),
singleaxis_opts=opts.SingleAxisOpts(type_="time", pos_bottom="10%"),
)
return tr.render_notebook()
[docs] def v_tradevolume(self, freq="D"):
"""
visualization on trade summary of the funds combination
:param freq: one character string, frequency label, now supporting D for date,
W for week and M for month, namely the trade volume is shown based on the time unit
:returns: pyecharts.Bar()
"""
return vtradevolume(self.totcftable, freq=freq)
[docs]class mulfix(mul, indicator):
"""
introduce cash to make a closed investment system, where netvalue analysis can be applied
namely the totcftable only has one row at the very beginning
:param fundtradeobj: trade obj to be include
:param status: status table, if no trade obj is provided, it will include all fund
based on code in status table
:param fetch: boolean, when open the fetch option, info class will try fetching from local files first in the init
:param save: boolean, when open the save option, info classes automatically save the class to files
:param path: string, the file path prefix of IO, or object or engine from sqlalchemy to connect sql database
:param form: string, the format of IO, options including: 'csv','sql'
:param totmoney: positive float, the total money as the input at the beginning
:param cashobj: cashinfo object, which is designed to balance the cash in and out
"""
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 _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)
[docs] def unitvalue(self, date=yesterdayobj()):
"""
:returns: float at unitvalue of the whole investment combination
"""
date = convert_date(date)
res = 0
for fund in self.fundtradeobj:
res += fund.briefdailyreport(date).get("currentvalue", 0)
return res / self.totmoney