import json import logging from datetime import datetime as dt from typing import List from py_jftech import component, autowired, format_date from pymysql import IntegrityError, constants from api import PortfoliosBuilder, PortfoliosRisk, AssetPool, Navs, PortfoliosType, Datum, SolveType, SolverFactory, \ RoboReportor, DatumType from portfolios.dao import robo_mpt_portfolios as rmp from portfolios.dao.robo_mpt_portfolios import get_list logger = logging.getLogger(__name__) @component(bean_name='mpt') class MptPortfoliosBuilder(PortfoliosBuilder): @autowired def __init__(self, assets: AssetPool = None, navs: Navs = None, datum: Datum = None, factory: SolverFactory = None): self._assets = assets self._navs = navs self._datum = datum self._factory = factory def get_portfolios(self, day, risk: PortfoliosRisk, type: PortfoliosType = PortfoliosType.NORMAL): try: portfolio = rmp.get_one(day, type, risk) if not portfolio: result = self.build_portfolio(day, type) for build_risk, datas in result.items(): try: rmp.insert({ **datas, 'risk': build_risk, 'type': type, 'date': day }) except IntegrityError as e: code, msg = e.args if code != constants.ER.DUP_ENTRY: raise e portfolio = rmp.get_one(day, type, risk) if SolveType(portfolio['solve']) is not SolveType.INFEASIBLE: result = json.loads(portfolio['portfolio']) return {int(x[0]): x[1] for x in result.items()} return None except Exception as e: logger.exception( f"build protfolio of type[{type.name}] and risk[{risk.name}] with date[{format_date(day)}] failure.", e) raise e def build_portfolio(self, day, type: PortfoliosType): result = {} portfolios = {} for risk in PortfoliosRisk: logger.info( f"start to build protfolio of type[{type.name}] and risk[{risk.name}] with date[{format_date(day)}]") solver = self._factory.create_solver(risk, type) navs_group = solver.reset_navs(day) for category, navs in navs_group.items(): # count = solver.get_config('asset-count')[0] # nav_count = len(navs.columns) # if count <= nav_count: # pass solver.set_navs(navs) solver.set_category(category) logger.debug({ 'Khist': len(solver.rtn_history), 'beta': solver.get_config('mpt.cvar-beta'), 'Kbeta': solver.k_beta, }) max_rtn, max_var, minCVaR_whenMaxR = solver.solve_max_rtn() min_rtn, min_var, maxCVaR_whenMinV = solver.solve_min_rtn() portfolio, cvar = solver.solve_mpt(min_rtn, max_rtn) portfolios = {**portfolios, **portfolio} result[risk] = { 'solve': SolveType.MPT, 'portfolio': json.dumps(portfolios), } if portfolios else { 'solve': SolveType.INFEASIBLE } return result def clear(self, day=None, risk: PortfoliosRisk = None): rmp.delete(min_date=day, risk=risk) def get_all_portfolios(self, risk: PortfoliosRisk = None): return get_list(risk=risk) @component(bean_name='poem') class PoemPortfoliosBuilder(MptPortfoliosBuilder): def build_portfolio(self, day, type: PortfoliosType): result = {} portfolios = {} for risk in PortfoliosRisk: solver = self._factory.create_solver(risk, type) navs_group = solver.reset_navs(day) for category, navs in navs_group.items(): solver.set_navs(navs) solver.set_category(category) max_rtn, max_var, minCVaR_whenMaxR = solver.solve_max_rtn() min_rtn, min_var, maxCVaR_whenMinV = solver.solve_min_rtn() mpt_portfolio, mpt_cvar = solver.solve_mpt(min_rtn, max_rtn) portfolio, cvar = solver.solve_poem(min_rtn, max_rtn, mpt_cvar, maxCVaR_whenMinV) if not portfolio: portfolio = mpt_portfolio portfolios = {**portfolios, **portfolio} if portfolios: result[risk] = { 'solve': SolveType.POEM, 'portfolio': json.dumps(portfolios), } return result @component(bean_name='signal-report') class SignalReportor(RoboReportor): @autowired def __init__(self, datum: Datum = None): self._datum = datum @property def report_name(self) -> str: return '调仓信号' def load_report(self, max_date=dt.today(), min_date=None) -> List[dict]: result = [] datums = {str(x['id']): x for x in self._datum.get_datums(type=DatumType.FUND, exclude=False)} for signal in rmp.get_list(max_date=max_date, min_date=min_date): for fund_id, weight in json.loads(signal['portfolio']).items(): result.append({ 'risk': PortfoliosRisk(signal['risk']).name, 'rebalance_date': signal['date'], 'portfolio_type': PortfoliosType.NORMAL.name, 'ft_ticker': datums[fund_id]['ftTicker'], 'blooberg_ticker': datums[fund_id]['bloombergTicker'], 'fund_name': datums[fund_id]['chineseName'], 'weight': weight }) return result