Commit 495d15b0 authored by 纪超's avatar 纪超

完成日志、配置文件、数据库工具模块

parent db33dba7
from framework import parse_date, get_quarter_start, get_config
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from enum import Enum, unique from enum import Enum, unique
from dateutil.relativedelta import relativedelta
@unique @unique
...@@ -158,9 +156,20 @@ class PortfoliosBuilder(ABC): ...@@ -158,9 +156,20 @@ class PortfoliosBuilder(ABC):
@abstractmethod @abstractmethod
def get_portfolios(self, day, risk: PortfoliosRisk, type: PortfoliosType = PortfoliosType.NORMAL): def get_portfolios(self, day, risk: PortfoliosRisk, type: PortfoliosType = PortfoliosType.NORMAL):
''' '''
获取指定日期,指定风险等级的投资组合 获取指定日期,指定风险等级,指定类型的投资组合
:param type: 投组的类型
:param day: 指定日期 :param day: 指定日期
:param risk: 风险等级 :param risk: 风险等级
:return: 资产组合字典{id: weight} :return: 资产组合字典{id: weight}
''' '''
pass pass
@abstractmethod
def build_portfolio(self, day, type: PortfoliosType):
'''
构建指定日期,指定类型的投资组合
:param day: 指定日期
:param type: 指定类型
:return 投资组合数据{risk: {...}},计算明细数据 {...}
'''
pass
import json import json
import pandas as pd
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from datetime import datetime as dt
import pandas as pd
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from empyrical import sortino_ratio from empyrical import sortino_ratio
from framework import filter_weekend, dict_remove, get_config, component, autowired, get_quarter_start
from api import AssetOptimize, Navs, BusinessException, Datum, AssetPoolType from api import AssetOptimize, Navs, BusinessException, Datum, AssetPoolType
from asset_pool.dao import robo_assets_pool as rop from asset_pool.dao import robo_assets_pool as rop
from datetime import datetime as dt from framework import filter_weekend, dict_remove, get_config, component, autowired, get_quarter_start
class SortinoAssetOptimize(AssetOptimize, ABC): class SortinoAssetOptimize(AssetOptimize, ABC):
...@@ -88,7 +90,8 @@ class FundSortinoAssetOptimize(SortinoAssetOptimize): ...@@ -88,7 +90,8 @@ class FundSortinoAssetOptimize(SortinoAssetOptimize):
def get_pct_change(self, fund_ids, day): def get_pct_change(self, fund_ids, day):
if not self._config: if not self._config:
raise BusinessException(f"find optimize, but not found sortino config.") raise BusinessException(f"find optimize, but not found sortino config.")
start = filter_weekend(sorted([day - relativedelta(days=1, **dict_remove(x, ('weight', 'name'))) for x in self._config])[0]) start = filter_weekend(
sorted([day - relativedelta(days=1, **dict_remove(x, ('weight', 'name'))) for x in self._config])[0])
fund_navs = pd.DataFrame(self._navs.get_fund_navs(fund_ids=tuple(fund_ids), min_date=start, max_date=day)) fund_navs = pd.DataFrame(self._navs.get_fund_navs(fund_ids=tuple(fund_ids), min_date=start, max_date=day))
fund_navs.sort_values('nav_date', inplace=True) fund_navs.sort_values('nav_date', inplace=True)
fund_navs = fund_navs.pivot_table(index='nav_date', columns='fund_id', values='nav_cal') fund_navs = fund_navs.pivot_table(index='nav_date', columns='fund_id', values='nav_cal')
......
from framework import component, autowired
from api import AssetPool, AssetOptimize, AssetRisk
from datetime import datetime as dt from datetime import datetime as dt
from api import AssetPool, AssetOptimize, AssetRisk
from framework import component, autowired
@component @component
class FundAssetPool(AssetPool): class FundAssetPool(AssetPool):
...@@ -14,4 +15,4 @@ class FundAssetPool(AssetPool): ...@@ -14,4 +15,4 @@ class FundAssetPool(AssetPool):
def get_pool(self, day=dt.today()): def get_pool(self, day=dt.today()):
opti_pool = self._optimize.get_optimize_pool(day) opti_pool = self._optimize.get_optimize_pool(day)
risk_pool = self._risk.get_risk_pool(day) risk_pool = self._risk.get_risk_pool(day)
return [x for x in opti_pool if x not in risk_pool] return [x for x in opti_pool if x not in risk_pool]
\ No newline at end of file
import json import json
import logging
from datetime import datetime as dt from datetime import datetime as dt
import pandas as pd import pandas as pd
...@@ -7,7 +8,9 @@ from scipy.stats import norm ...@@ -7,7 +8,9 @@ from scipy.stats import norm
from api import AssetRisk, Navs, AssetRiskDateType as DateType, Datum, AssetPoolType from api import AssetRisk, Navs, AssetRiskDateType as DateType, Datum, AssetPoolType
from asset_pool.dao import asset_risk_dates as ard, asset_ewma_value as aev, robo_assets_pool as rap from asset_pool.dao import asset_risk_dates as ard, asset_ewma_value as aev, robo_assets_pool as rap
from framework import component, autowired, get_config, log, format_date, block_execute from framework import component, autowired, get_config, format_date, block_execute
logger = logging.getLogger(__name__)
def get_risk_start_date(): def get_risk_start_date():
...@@ -38,7 +41,6 @@ class CvarEwmaAssetRisk(AssetRisk): ...@@ -38,7 +41,6 @@ class CvarEwmaAssetRisk(AssetRisk):
return json.loads(asset_pool['asset_ids']) return json.loads(asset_pool['asset_ids'])
def is_risk(self, id, day) -> bool: def is_risk(self, id, day) -> bool:
print(id, day)
asset_pool = rap.get_one(day, AssetPoolType.RISK) asset_pool = rap.get_one(day, AssetPoolType.RISK)
if asset_pool: if asset_pool:
return id in json.loads(asset_pool['asset_ids']) return id in json.loads(asset_pool['asset_ids'])
...@@ -49,11 +51,11 @@ class CvarEwmaAssetRisk(AssetRisk): ...@@ -49,11 +51,11 @@ class CvarEwmaAssetRisk(AssetRisk):
def build_risk_date(self, asset_id, day=dt.today()): def build_risk_date(self, asset_id, day=dt.today()):
risk_date = not None risk_date = not None
try: try:
log.debug(f"start build risk date for asset[{asset_id}] to date[{format_date(day)}]") logger.info(f"start build risk date for asset[{asset_id}] to date[{format_date(day)}]")
while risk_date is not None: while risk_date is not None:
risk_date = self.get_next_date(asset_id, day=day) risk_date = self.get_next_date(asset_id, day=day)
except Exception as e: except Exception as e:
log.exception(f"build risk date for asset[{asset_id}] after date[{risk_date}] error", e) logger.exception(f"build risk date for asset[{asset_id}] after date[{risk_date}] error", e)
def get_next_date(self, asset_id, day=dt.today()): def get_next_date(self, asset_id, day=dt.today()):
last = ard.get_last_one(asset_id, day) last = ard.get_last_one(asset_id, day)
...@@ -83,7 +85,8 @@ class CvarEwmaAssetRisk(AssetRisk): ...@@ -83,7 +85,8 @@ class CvarEwmaAssetRisk(AssetRisk):
if row['rtn'] < rtns[rtns.date == cvar_start_date].iloc[0].rtn: if row['rtn'] < rtns[rtns.date == cvar_start_date].iloc[0].rtn:
# 当日回报率跌破最低点, 则直接触发 # 当日回报率跌破最低点, 则直接触发
tigger = True tigger = True
elif row['rtn'] <= self._config['cvar']['threshold'] and len(cvar_rtns) >= self._config['cvar']['min-volume']: elif row['rtn'] <= self._config['cvar']['threshold'] and len(cvar_rtns) >= self._config['cvar'][
'min-volume']:
# 当日回报率小于等于阀值并且有足够cvar累计计算数据,则计算cvar判断 # 当日回报率小于等于阀值并且有足够cvar累计计算数据,则计算cvar判断
alpha = 1 - self._config['cvar']['coef'] alpha = 1 - self._config['cvar']['coef']
mean = cvar_rtns['rtn'].mean() mean = cvar_rtns['rtn'].mean()
......
from framework import read, write, where, format_date from framework import read, write, where, format_date
__COLUMNS__ = { __COLUMNS__ = {
'aev_id': 'id', 'aev_id': 'id',
'aev_date': 'date', 'aev_date': 'date',
...@@ -30,7 +29,7 @@ def get_last_one(asset_id, max_date=None): ...@@ -30,7 +29,7 @@ def get_last_one(asset_id, max_date=None):
@read @read
def get_list(asset_id, min_date=None, max_date=None): def get_list(asset_id, min_date=None, max_date=None):
sqls =[] sqls = []
if min_date: if min_date:
sqls.append(f"aev_date >= '{format_date(min_date)}'") sqls.append(f"aev_date >= '{format_date(min_date)}'")
if max_date: if max_date:
......
from framework import read, write, where, format_date
from api import AssetRiskDateType as DateType from api import AssetRiskDateType as DateType
from framework import read, write, where, format_date
__COLUMNS__ = { __COLUMNS__ = {
'ard_id': 'id', 'ard_id': 'id',
...@@ -28,4 +28,3 @@ def get_last_one(fund_id, date, type: DateType = None): ...@@ -28,4 +28,3 @@ def get_last_one(fund_id, date, type: DateType = None):
select {','.join([f"`{x[0]}` as `{x[1]}`" for x in __COLUMNS__.items()])} select {','.join([f"`{x[0]}` as `{x[1]}`" for x in __COLUMNS__.items()])}
from asset_risk_dates {where(sql, **kwargs)} order by ard_date desc, ard_type asc limit 1 from asset_risk_dates {where(sql, **kwargs)} order by ard_date desc, ard_type asc limit 1
''' '''
import json import json
from framework import read, write, where, format_date
from api import AssetPoolType from api import AssetPoolType
from framework import read, write, where, format_date
__COLUMNS__ = { __COLUMNS__ = {
'rap_id': 'id', 'rap_id': 'id',
......
from framework import read, where, format_date, to_tuple from framework import read, where, format_date, to_tuple
__COLUMNS__ = { __COLUMNS__ = {
'rfn_fund_id': 'fund_id', 'rfn_fund_id': 'fund_id',
'rfn_date': 'nav_date', 'rfn_date': 'nav_date',
......
import json import json
from api import DatumType, Datum from api import DatumType, Datum
from basic.dao import robo_base_datum as rbd from basic.dao import robo_base_datum as rbd
from framework import component, parse_date from framework import component, parse_date
......
import pandas as pd import pandas as pd
from api import Navs, Datum from api import Navs, Datum
from basic.dao import robo_exrate as re, robo_fund_navs as rfn from basic.dao import robo_exrate as re, robo_fund_navs as rfn
from framework import get_config, component, autowired from framework import get_config, component, autowired
...@@ -18,7 +19,8 @@ class DefaultNavs(Navs): ...@@ -18,7 +19,8 @@ class DefaultNavs(Navs):
navs = pd.DataFrame(navs) navs = pd.DataFrame(navs)
navs = navs.pivot_table(index='nav_date', columns='fund_id', values='nav_cal') navs = navs.pivot_table(index='nav_date', columns='fund_id', values='nav_cal')
for exrate_config in self._config['exrate']: for exrate_config in self._config['exrate']:
exrate = pd.DataFrame(re.get_exrates(ticker=exrate_config['ticker'], min_date=navs.index.min(), max_date=navs.index.max())) exrate = pd.DataFrame(re.get_exrates(ticker=exrate_config['ticker'], min_date=navs.index.min(),
max_date=navs.index.max()))
exrate = exrate[['date', 'close']] exrate = exrate[['date', 'close']]
exrate.set_index('date', inplace=True) exrate.set_index('date', inplace=True)
for fund in self._datum.get_fund_datums(crncy=exrate_config['from']): for fund in self._datum.get_fund_datums(crncy=exrate_config['from']):
...@@ -30,7 +32,3 @@ class DefaultNavs(Navs): ...@@ -30,7 +32,3 @@ class DefaultNavs(Navs):
navs.sort_values(by=['fund_id', 'nav_date'], inplace=True) navs.sort_values(by=['fund_id', 'nav_date'], inplace=True)
navs = navs.to_dict('records') navs = navs.to_dict('records')
return navs return navs
if __name__ == '__main__':
print(isinstance((), tuple))
...@@ -16,10 +16,10 @@ framework: ...@@ -16,10 +16,10 @@ framework:
user: jft-ra@thizgroup.com user: jft-ra@thizgroup.com
password: 5dbb#30ec6d3 password: 5dbb#30ec6d3
mulit-process: mulit-process:
max-workers: 4 max-workers: 8
logger: logger:
version: 1 version: 1
use: ${LOG_NAME:root} use: prod
formatters: formatters:
brief: brief:
format: "%(asctime)s - %(levelname)s - %(message)s" format: "%(asctime)s - %(levelname)s - %(message)s"
...@@ -29,7 +29,7 @@ framework: ...@@ -29,7 +29,7 @@ framework:
console: console:
class: logging.StreamHandler class: logging.StreamHandler
formatter: simple formatter: simple
level: INFO level: DEBUG
stream: ext://sys.stdout stream: ext://sys.stdout
file: file:
class: logging.handlers.TimedRotatingFileHandler class: logging.handlers.TimedRotatingFileHandler
...@@ -42,9 +42,11 @@ framework: ...@@ -42,9 +42,11 @@ framework:
when: D when: D
loggers: loggers:
prod: prod:
handlers: [ console,file ] handlers: [ console, file ]
level: INFO level: INFO
propagate: 0 propagate: no
portfolios:
level: DEBUG
root: root:
level: INFO level: INFO
handlers: [ console ] handlers: [ console ]
......
...@@ -2,9 +2,9 @@ from .date_utils import * ...@@ -2,9 +2,9 @@ from .date_utils import *
from .base import * from .base import *
from .database import read, write, transaction, where, to_columns from .database import read, write, transaction, where, to_columns
from .env_config import config, get_config from .env_config import config, get_config
from .logger import build_logger, logger as log from .logs import build_logger, get_logger
from .injectable import component, autowired, get_instance, init_injectable as _init_injectable from .injectable import component, autowired, get_instance, init_injectable as _init_injectable
from .mulit_process import process_pool, create_process_pool, block_execute from .mulit_process import process_pool, create_process_pool, block_execute
_init_injectable() _init_injectable()
del injectable, logger, env_config, database, base, date_utils, _init_injectable, mulit_process del injectable, logs, env_config, database, base, date_utils, _init_injectable, mulit_process
...@@ -4,7 +4,7 @@ from functools import partial ...@@ -4,7 +4,7 @@ from functools import partial
import yaml import yaml
from .base import * from framework.base import *
has_regex_module = False has_regex_module = False
ENV_VAR_MATCHER = re.compile( ENV_VAR_MATCHER = re.compile(
...@@ -17,7 +17,6 @@ ENV_VAR_MATCHER = re.compile( ...@@ -17,7 +17,6 @@ ENV_VAR_MATCHER = re.compile(
""", re.VERBOSE """, re.VERBOSE
) )
IMPLICIT_ENV_VAR_MATCHER = re.compile( IMPLICIT_ENV_VAR_MATCHER = re.compile(
r""" r"""
.* # matches any number of any characters .* # matches any number of any characters
...@@ -27,7 +26,6 @@ IMPLICIT_ENV_VAR_MATCHER = re.compile( ...@@ -27,7 +26,6 @@ IMPLICIT_ENV_VAR_MATCHER = re.compile(
""", re.VERBOSE """, re.VERBOSE
) )
RECURSIVE_ENV_VAR_MATCHER = re.compile( RECURSIVE_ENV_VAR_MATCHER = re.compile(
r""" r"""
\$\{ # match characters `${` literally \$\{ # match characters `${` literally
...@@ -89,7 +87,9 @@ yaml.add_implicit_resolver( ...@@ -89,7 +87,9 @@ yaml.add_implicit_resolver(
config = build_config() config = build_config()
def get_config(module: str = None): def get_config(module: str = None, file: str = None):
if module == '__main__':
module = file
result = config result = config
if module: if module:
for name in [x.replace('_', '-') for x in module.split('.')]: for name in [x.replace('_', '-') for x in module.split('.')]:
......
import logging
import os import os
from logging import config as cf, getLogger from logging import config as cf
from framework.env_config import get_config
from framework.base import get_project_path from framework.base import get_project_path
from framework.env_config import get_config
def build_logger(config, name='root'): def build_logger(config):
if 'handlers' in config and 'file' in config['handlers']: if 'handlers' in config and 'file' in config['handlers']:
file = config['handlers']['file'] file = config['handlers']['file']
path = os.path.join(get_project_path(), file["filename"]) path = os.path.join(get_project_path(), file["filename"])
os.makedirs(os.path.split(path)[0], exist_ok=True) os.makedirs(os.path.split(path)[0], exist_ok=True)
file["filename"] = os.path.abspath(path) file["filename"] = os.path.abspath(path)
cf.dictConfig(config) cf.dictConfig(config)
return getLogger(name)
config = get_config(__name__) config = get_config("framework.logger")
logger = build_logger(config, name=config['use'] if 'use' in config else None) if config else None if config:
build_logger(config)
def get_logger(name=None):
return logging.getLogger(config['use'] if 'use' in config and config['use'] is not None else name)
from concurrent.futures import ProcessPoolExecutor, as_completed from concurrent.futures import ProcessPoolExecutor, as_completed
from framework.env_config import get_config from framework.env_config import get_config
from functools import partial, wraps
config = get_config(__name__) config = get_config(__name__)
process_pool = ProcessPoolExecutor(max_workers=config['max-workers'] or 2) process_pool = ProcessPoolExecutor(max_workers=config['max-workers'] or 2)
......
from framework import autowired, parse_date, log from framework import autowired, parse_date, get_logger
from api import PortfoliosBuilder, PortfoliosRisk from api import PortfoliosBuilder, PortfoliosRisk
logger = get_logger('main')
@autowired
@autowired(names={'builder': 'poem'})
def start(builder: PortfoliosBuilder = None): def start(builder: PortfoliosBuilder = None):
day = parse_date('2022-11-07') day = parse_date('2022-11-07')
log.info(builder.get_portfolios(day, PortfoliosRisk.FT3)) logger.info(builder.get_portfolios(day, PortfoliosRisk.FT3))
def test(arg):
if arg:
return 1, 1
else:
return None
if __name__ == '__main__': if __name__ == '__main__':
log.info("start") logger.info("info")
start() logger.debug('debug')
\ No newline at end of file logger.warning('warning')
logger.error('error')
logger.critical('critical')
# start()
import json
import os import os
import sys import sys
import json
import pandas as pd import pandas as pd
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
...@@ -8,9 +8,11 @@ from numpy import NAN ...@@ -8,9 +8,11 @@ from numpy import NAN
from pyomo.environ import * from pyomo.environ import *
from api import PortfoliosBuilder, PortfoliosRisk, AssetPool, Navs, PortfoliosType, Datum, SolveType from api import PortfoliosBuilder, PortfoliosRisk, AssetPool, Navs, PortfoliosType, Datum, SolveType
from framework import component, autowired, get_config from framework import component, autowired, get_config, format_date, get_logger
from portfolios.dao import robo_mpt_portfolios as rmp from portfolios.dao import robo_mpt_portfolios as rmp
logger = get_logger(__name__)
def create_solver(): def create_solver():
if sys.platform.find('win') == 0: if sys.platform.find('win') == 0:
...@@ -24,7 +26,8 @@ def create_solver(): ...@@ -24,7 +26,8 @@ def create_solver():
class MptSolver: class MptSolver:
@autowired @autowired
def __init__(self, risk: PortfoliosRisk, type: PortfoliosType, assets: AssetPool = None, navs: Navs = None, datum: Datum = None): def __init__(self, risk: PortfoliosRisk, type: PortfoliosType, assets: AssetPool = None, navs: Navs = None,
datum: Datum = None):
self.__navs = None self.__navs = None
self.risk = risk self.risk = risk
self.type = type or PortfoliosType.NORMAL self.type = type or PortfoliosType.NORMAL
...@@ -59,17 +62,32 @@ class MptSolver: ...@@ -59,17 +62,32 @@ class MptSolver:
result = self.rtn_matrix * 12 result = self.rtn_matrix * 12
return result.values return result.values
@property
def beta(self):
return self.get_config('mpt.cvar-beta')
@property @property
def k_beta(self): def k_beta(self):
return round(len(self.rtn_history) * self.get_config('mpt.cvar-beta') + 0.499999) return round(len(self.rtn_history) * self.beta + 0.499999)
@property
def pct_value(self):
return self.get_config('mpt.quantile')
def solve_max_rtn(self): def solve_max_rtn(self):
model = self.create_model() model = self.create_model()
model.objective = Objective(expr=sum([model.w[i] * self.rtn_annualized[i] for i in model.indices]), sense=maximize) model.objective = Objective(expr=sum([model.w[i] * self.rtn_annualized[i] for i in model.indices]),
sense=maximize)
self._solver.solve(model) self._solver.solve(model)
self.debug_solve_result(model)
max_rtn = self.calc_port_rtn(model) max_rtn = self.calc_port_rtn(model)
max_var = self.calc_port_var(model) max_var = self.calc_port_var(model)
minCVaR_whenMaxR = self.calc_port_cvar(model) minCVaR_whenMaxR = self.calc_port_cvar(model)
logger.debug({
'max_rtn': max_rtn,
'max_var': max_var,
'minCVaR_whenMaxR': minCVaR_whenMaxR,
})
return max_rtn, max_var, minCVaR_whenMaxR return max_rtn, max_var, minCVaR_whenMaxR
def solve_min_rtn(self): def solve_min_rtn(self):
...@@ -78,13 +96,21 @@ class MptSolver: ...@@ -78,13 +96,21 @@ class MptSolver:
expr=sum([model.w[i] * model.w[j] * self.sigma.iloc[i, j] for i in model.indices for j in model.indices]), expr=sum([model.w[i] * model.w[j] * self.sigma.iloc[i, j] for i in model.indices for j in model.indices]),
sense=minimize) sense=minimize)
self._solver.solve(model) self._solver.solve(model)
self.debug_solve_result(model)
min_rtn = self.calc_port_rtn(model) min_rtn = self.calc_port_rtn(model)
min_var = self.calc_port_var(model) min_var = self.calc_port_var(model)
maxCVaR_whenMinV = self.calc_port_cvar(model) maxCVaR_whenMinV = self.calc_port_cvar(model)
logger.debug({
'min_rtn': min_rtn,
'min_var': min_var,
'maxCVaR_whenMinV': maxCVaR_whenMinV,
})
return min_rtn, min_var, maxCVaR_whenMinV return min_rtn, min_var, maxCVaR_whenMinV
def solve_mpt(self, min_rtn, max_rtn): def solve_mpt(self, min_rtn, max_rtn):
big_y = min_rtn + self.get_config('mpt.quantile') * (max_rtn - min_rtn) logger.debug(f'...... ...... ...... ...... ...... ...... ...... ...... MPT ... sub risk : pct_value = {self.pct_value}')
big_y = min_rtn + self.pct_value * (max_rtn - min_rtn)
logger.debug(f'big_Y = target_Return = {big_y}')
model = self.create_model() model = self.create_model()
model.cons_rtn = Constraint(expr=sum([model.w[i] * self.rtn_annualized[i] for i in model.indices]) >= big_y) model.cons_rtn = Constraint(expr=sum([model.w[i] * self.rtn_annualized[i] for i in model.indices]) >= big_y)
model.objective = Objective( model.objective = Objective(
...@@ -92,28 +118,37 @@ class MptSolver: ...@@ -92,28 +118,37 @@ class MptSolver:
sense=minimize) sense=minimize)
result = self._solver.solve(model) result = self._solver.solve(model)
if result.solver.termination_condition == TerminationCondition.infeasible: if result.solver.termination_condition == TerminationCondition.infeasible:
logger.debug('...... MPT: Infeasible Optimization Problem.')
return None, None return None, None
logger.debug('...... MPT: Has solution.')
self.debug_solve_result(model)
return self.calc_port_weight(model), self.calc_port_cvar(model) return self.calc_port_weight(model), self.calc_port_cvar(model)
def solve_poem(self, min_rtn, max_rtn, base_cvar, max_cvar): def solve_poem(self, min_rtn, max_rtn, base_cvar, max_cvar):
k_history = len(self.rtn_history) k_history = len(self.rtn_history)
quantile = self.get_config('mpt.quantile') quantile = self.pct_value
logger.debug(f'...... ...... ...... ...... ...... ...... ...... ...... POEM With CVaR constraints ... sub risk : pct_value = {quantile}')
big_y = min_rtn + quantile * (max_rtn - min_rtn) big_y = min_rtn + quantile * (max_rtn - min_rtn)
small_y = base_cvar + (max_cvar - base_cvar) * self.get_config('poem.cvar-scale-factor') * quantile small_y = base_cvar + (max_cvar - base_cvar) * self.get_config('poem.cvar-scale-factor') * quantile
logger.debug(f'big_Y = target_Return = {big_y} | small_y = target_cvar = {small_y}')
model = self.create_model() model = self.create_model()
model.alpha = Var(domain=Reals) model.alpha = Var(domain=Reals)
model.x = Var(range(k_history), domain=NonNegativeReals) model.x = Var(range(k_history), domain=NonNegativeReals)
model.cons_cvar_aux = Constraint(range(k_history), rule=lambda m, k: m.x[k] >= m.alpha - sum([m.w[i] * self.rtn_history[k][i] for i in m.indices])) model.cons_cvar_aux = Constraint(range(k_history), rule=lambda m, k: m.x[k] >= m.alpha - sum(
[m.w[i] * self.rtn_history[k][i] for i in m.indices]))
model.cons_rtn = Constraint(expr=sum([model.w[i] * self.rtn_annualized[i] for i in model.indices]) >= big_y) model.cons_rtn = Constraint(expr=sum([model.w[i] * self.rtn_annualized[i] for i in model.indices]) >= big_y)
model.cons_cvar = Constraint(expr=model.alpha - (1 / self.k_beta) * sum([model.x[k] for k in range(k_history)]) >= small_y) model.cons_cvar = Constraint(
expr=model.alpha - (1 / self.k_beta) * sum([model.x[k] for k in range(k_history)]) >= small_y)
result = self._solver.solve(model) result = self._solver.solve(model)
if result.solver.termination_condition == TerminationCondition.infeasible: if result.solver.termination_condition == TerminationCondition.infeasible:
logger.debug('...... POEM: Infeasible Optimization Problem.')
return None, None return None, None
logger.debug('...... POEM: Has solution.')
self.debug_solve_result(model)
return self.calc_port_weight(model), self.calc_port_cvar(model) return self.calc_port_weight(model), self.calc_port_cvar(model)
def calc_port_weight(self, model): def calc_port_weight(self, model):
id_list = self._navs.columns id_list = self.navs.columns
weight_list = [] weight_list = []
for i in model.indices: for i in model.indices:
weight_list.append(model.w[i]._value * model.z[i]._value) weight_list.append(model.w[i]._value * model.z[i]._value)
...@@ -140,12 +175,14 @@ class MptSolver: ...@@ -140,12 +175,14 @@ class MptSolver:
return sum([model.w[i]._value * self.rtn_annualized[i] for i in model.indices]) return sum([model.w[i]._value * self.rtn_annualized[i] for i in model.indices])
def calc_port_var(self, model): def calc_port_var(self, model):
return sum([model.w[i]._value * model.w[j]._value * self.sigma.iloc[i, j] for i in model.indices for j in model.indices]) return sum([model.w[i]._value * model.w[j]._value * self.sigma.iloc[i, j] for i in model.indices for j in
model.indices])
def calc_port_cvar(self, model): def calc_port_cvar(self, model):
port_r_hist = [] port_r_hist = []
for k in range(len(self.rtn_history)): for k in range(len(self.rtn_history)):
port_r_hist.append(sum([model.w[i]._value * model.z[i]._value * self.rtn_history[k][i] for i in model.indices])) port_r_hist.append(
sum([model.w[i]._value * model.z[i]._value * self.rtn_history[k][i] for i in model.indices]))
port_r_hist.sort() port_r_hist.sort()
return sum(port_r_hist[0: self.k_beta]) / self.k_beta return sum(port_r_hist[0: self.k_beta]) / self.k_beta
...@@ -161,11 +198,12 @@ class MptSolver: ...@@ -161,11 +198,12 @@ class MptSolver:
model = ConcreteModel() model = ConcreteModel()
model.indices = range(0, len(self._navs.columns)) model.indices = range(0, len(self.navs.columns))
model.w = Var(model.indices, domain=NonNegativeReals) model.w = Var(model.indices, domain=NonNegativeReals)
model.z = Var(model.indices, domain=Binary) model.z = Var(model.indices, domain=Binary)
model.cons_sum_weight = Constraint(expr=sum([model.w[i] for i in model.indices]) == 1) model.cons_sum_weight = Constraint(expr=sum([model.w[i] for i in model.indices]) == 1)
model.cons_num_asset = Constraint(expr=inequality(min_count, sum([model.z[i] for i in model.indices]), max_count, strict=False)) model.cons_num_asset = Constraint(
expr=inequality(min_count, sum([model.z[i] for i in model.indices]), max_count, strict=False))
model.cons_bounds_low = Constraint(model.indices, rule=lambda m, i: m.z[i] * low_weight <= m.w[i]) model.cons_bounds_low = Constraint(model.indices, rule=lambda m, i: m.z[i] * low_weight <= m.w[i])
model.cons_bounds_up = Constraint(model.indices, rule=lambda m, i: m.z[i] * high_weight >= m.w[i]) model.cons_bounds_up = Constraint(model.indices, rule=lambda m, i: m.z[i] * high_weight >= m.w[i])
return model return model
...@@ -184,9 +222,11 @@ class MptSolver: ...@@ -184,9 +222,11 @@ class MptSolver:
navs = navs.sort_index() navs = navs.sort_index()
navs_nan = navs.isna().sum() navs_nan = navs.isna().sum()
navs.drop(columns=[x for x in navs_nan.index if navs_nan.loc[x] >= self.get_config('navs.max-nan.asset')], inplace=True) navs.drop(columns=[x for x in navs_nan.index if navs_nan.loc[x] >= self.get_config('navs.max-nan.asset')],
inplace=True)
navs_nan = navs.apply(lambda r: r.isna().sum() / len(r), axis=1) navs_nan = navs.apply(lambda r: r.isna().sum() / len(r), axis=1)
navs.drop(index=[x for x in navs_nan.index if navs_nan.loc[x] >= self.get_config('navs.max-nan.day')], inplace=True) navs.drop(index=[x for x in navs_nan.index if navs_nan.loc[x] >= self.get_config('navs.max-nan.day')],
inplace=True)
navs.fillna(method='ffill', inplace=True) navs.fillna(method='ffill', inplace=True)
self.__navs = navs self.__navs = navs
...@@ -198,11 +238,31 @@ class MptSolver: ...@@ -198,11 +238,31 @@ class MptSolver:
else: else:
return None return None
return config return config
value = load_config(self._config[self.type.value] if self.type is not PortfoliosType.NORMAL else self._config) value = load_config(self._config[self.type.value] if self.type is not PortfoliosType.NORMAL else self._config)
if value is None: if value is None:
value = load_config(self._config) value = load_config(self._config)
return value[f'ft{self.risk.value}'] if value and isinstance(value, dict) else value return value[f'ft{self.risk.value}'] if value and isinstance(value, dict) else value
def debug_solve_result(self, model):
if logger.isEnabledFor(DEBUG):
logger.debug('===============================')
logger.debug('solution: id | w(id)')
w_sum = 0
for i in model.indices:
if model.z[i]._value == 1:
logger.debug(f'{self.navs.columns[i]} | {model.w[i]._value}')
w_sum += model.w[i]._value
logger.debug(f'w_sum = {w_sum}')
logger.debug({
'beta': self.beta,
'kbeta': self.k_beta,
'port_R': self.calc_port_rtn(model),
'port_V': self.calc_port_cvar(model),
'port_CVaR': self.calc_port_cvar(model)
})
logger.debug('-------------------------------')
@component(bean_name='mpt') @component(bean_name='mpt')
class MptPortfoliosBuilder(PortfoliosBuilder): class MptPortfoliosBuilder(PortfoliosBuilder):
...@@ -216,67 +276,71 @@ class MptPortfoliosBuilder(PortfoliosBuilder): ...@@ -216,67 +276,71 @@ class MptPortfoliosBuilder(PortfoliosBuilder):
def get_portfolios(self, day, risk: PortfoliosRisk, type: PortfoliosType = PortfoliosType.NORMAL): def get_portfolios(self, day, risk: PortfoliosRisk, type: PortfoliosType = PortfoliosType.NORMAL):
portfolio = rmp.get_one(day, type, risk) portfolio = rmp.get_one(day, type, risk)
if not portfolio: if not portfolio:
self.build_portfolio(day, type) result, detail = self.build_portfolio(day, type)
for build_risk, datas in result.items():
rmp.insert({
**{datas},
'risk': build_risk,
'type': type,
'date': day
})
portfolio = rmp.get_one(day, type, risk) portfolio = rmp.get_one(day, type, risk)
return json.loads(portfolio['portfolio']) if SolveType(portfolio['rmp_rolve']) is not SolveType.INFEASIBLE else None return json.loads(portfolio['portfolio']) if SolveType(portfolio['solve']) is not SolveType.INFEASIBLE else None
def build_portfolio(self, day, type: PortfoliosType): def build_portfolio(self, day, type: PortfoliosType):
result = {}
detail = {}
for risk in PortfoliosRisk: for risk in PortfoliosRisk:
logger.info(
f"start build protfolio of type[{type.name}] and risk[{risk.name}] with date[{format_date(day)}]")
solver = MptSolver(risk, type) solver = MptSolver(risk, type)
solver.reset_navs(day) solver.reset_navs(day)
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() max_rtn, max_var, minCVaR_whenMaxR = solver.solve_max_rtn()
min_rtn, min_var, maxCVaR_whenMinV = solver.solve_min_rtn() min_rtn, min_var, maxCVaR_whenMinV = solver.solve_min_rtn()
portfolio, cvar = solver.solve_mpt(min_rtn, max_rtn) portfolio, cvar = solver.solve_mpt(min_rtn, max_rtn)
if portfolio: result[risk] = {
rmp.insert({ 'solve': SolveType.MPT,
'date': day, 'portfolio': json.dumps(portfolio),
'risk': risk, 'cvar': cvar
'type': type, } if portfolio else {
'solve': SolveType.MPT, 'solve': SolveType.INFEASIBLE
'portfolio': json.dumps(portfolio), }
'cvar': cvar detail[risk] = {
}) 'max_rtn': max_rtn,
else: 'max_var': max_var,
rmp.insert({ 'minCVaR_whenMaxR': minCVaR_whenMaxR,
'date': day, 'min_rtn': min_rtn,
'risk': risk, 'min_var': min_var,
'type': type, 'maxCVaR_whenMinV': maxCVaR_whenMinV,
'solve': SolveType.INFEASIBLE }
}) return result, detail
@component(bean_name='poem') @component(bean_name='poem')
class PoemPortfoliosBuilder(MptPortfoliosBuilder): class PoemPortfoliosBuilder(MptPortfoliosBuilder):
def build_portfolio(self, day, type: PortfoliosType): def build_portfolio(self, day, type: PortfoliosType):
result, detail = super(PoemPortfoliosBuilder, self).build_portfolio(day, type)
for risk in PortfoliosRisk: for risk in PortfoliosRisk:
if result[risk]['solve'] is SolveType.INFEASIBLE:
continue
solver = MptSolver(risk, type) solver = MptSolver(risk, type)
solver.reset_navs(day) solver.reset_navs(day)
max_rtn, max_var, minCVaR_whenMaxR = solver.solve_max_rtn() min_rtn = detail[risk]['min_rtn']
min_rtn, min_var, maxCVaR_whenMinV = solver.solve_min_rtn() max_rtn = detail[risk]['max_rtn']
portfolio, cvar = solver.solve_mpt(min_rtn, max_rtn) mpt_cvar = result[risk]['cvar']
solve = SolveType.MPT maxCVaR_whenMinV = detail[risk]['maxCVaR_whenMinV']
if portfolio is not None: portfolio, cvar = solver.solve_poem(min_rtn, max_rtn, mpt_cvar, maxCVaR_whenMinV)
poem_port, poem_cvar = solver.solve_poem(min_rtn, max_rtn, cvar, maxCVaR_whenMinV)
if poem_port:
portfolio = poem_port
cvar = poem_cvar
solve = SolveType.POEM
if portfolio: if portfolio:
rmp.insert({ result[risk] = {
'date': day, 'solve': SolveType.POEM,
'risk': risk,
'type': type,
'solve': solve,
'portfolio': json.dumps(portfolio), 'portfolio': json.dumps(portfolio),
'cvar': cvar 'cvar': cvar
}) }
else: detail[risk]['mpt_cvar'] = mpt_cvar
rmp.insert({ return result, detail
'date': day,
'risk': risk,
'type': type,
'solve': SolveType.INFEASIBLE
})
from datetime import datetime from datetime import datetime
from framework import read, write, where, format_date
from enum import Enum from enum import Enum
from api import PortfoliosRisk, PortfoliosType from api import PortfoliosRisk, PortfoliosType
from framework import read, write, where, format_date
__COLUMNS__ = { __COLUMNS__ = {
'rmp_id': 'id', 'rmp_id': 'id',
...@@ -31,6 +32,6 @@ def insert(datas): ...@@ -31,6 +32,6 @@ def insert(datas):
@read(one=True) @read(one=True)
def get_one(day, type: PortfoliosType, risk: PortfoliosRisk): def get_one(day, type: PortfoliosType, risk: PortfoliosRisk):
return f''' return f'''
select {','.join([x for x in __COLUMNS__.keys()])} from robo_mpt_portfolios select {','.join([f"{x[0]} as {x[1]}" for x in __COLUMNS__.items()])} from robo_mpt_portfolios
{where(rmp_date=day, rmp_risk=risk, rmp_type=type)} {where(rmp_date=day, rmp_risk=risk, rmp_type=type)}
''' '''
from api import PortfoliosBuilder, PortfoliosType
from framework import autowired, parse_date, get_logger
logger = get_logger('test')
@autowired(names={'builder': 'poem'})
def test_portfolio_builder(builder: PortfoliosBuilder = None):
result, detail = builder.build_portfolio(parse_date('2011-11-07'), PortfoliosType.NORMAL)
logger.info(result)
logger.info(detail)
if __name__ == '__main__':
test_portfolio_builder()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment