import math from datetime import datetime as dt from typing import List import pandas as pd from py_jftech import component, autowired, filter_weekend, prev_workday from api import RoboReportor, PortfoliosRisk, PortfoliosHolder, Datum, DatumType, Navs, RoboExecutor @component(bean_name='contribution-report') class ContributionReportor(RoboReportor): @autowired def __init__(self, hold: PortfoliosHolder = None, datum: Datum = None, navs: Navs = None, exec: RoboExecutor = None): self._hold = hold self._datum = datum self._navs = navs self._exec = exec @property def report_name(self) -> str: return '贡献率' def load_report(self, max_date=dt.today(), min_date=None) -> List[dict]: max_date = filter_weekend(max_date) min_date = filter_weekend(min_date) if min_date is not None else self._exec.start_date result = pd.DataFrame() for risk in PortfoliosRisk.values(): buy_date = None sell_date = max_date while buy_date is None or sell_date > min_date: last_date = sell_date if sell_date == max_date else prev_workday(sell_date) buy_date = self._hold.get_last_rebalance_date(risk=risk, max_date=last_date) weight = self._hold.get_portfolios_weight(day=last_date, risk=risk) datums = pd.DataFrame(self._datum.get_datums(type=DatumType.FUND, datum_ids=tuple(weight.keys()))) datums = datums[['id', 'ftTicker', 'bloombergTicker', 'chineseName']] datums.columns = ['id', 'ft_ticker', 'bloomberg_ticker', 'name'] datums['ratio'] = datums.apply(lambda row: weight[row.id], axis=1) datums['hold'] = (sell_date - buy_date).days navs = pd.DataFrame(self._navs.get_fund_navs(fund_ids=tuple(weight.keys()), max_date=sell_date, min_date=buy_date)) navs = navs.pivot_table(columns='fund_id', index='nav_date', values='nav_cal') rtns = navs.iloc[-1] / navs.iloc[0] - 1 rtns.name = 'rtns' datums = datums.join(rtns, on='id') datums['risk'] = risk.name datums['buy_date'] = buy_date datums['sell_date'] = sell_date if sell_date != max_date else math.nan datums.drop('id', axis=1, inplace=True) result = pd.concat([result, datums], ignore_index=True) sell_date = buy_date if buy_date < sell_date else prev_workday(buy_date) return result.to_dict('records') if not result.empty else []