Commit 34ac0de5 authored by wenwen.tang's avatar wenwen.tang 😕
parent 5b121a27
......@@ -4,12 +4,12 @@ from sys import exception
import pandas as pd
from dateutil.relativedelta import relativedelta
from empyrical import sortino_ratio
from empyrical import sortino_ratio, annual_volatility
from py_jftech import filter_weekend, dict_remove, get_config, component, autowired, next_workday, \
is_workday
from api import AssetOptimize, Navs, Datum, AssetPoolType, DatumType
from asset_pool.dao import robo_assets_pool as rop
from asset_pool.dao import robo_assets_pool as rop, robo_indicator
class SortinoAssetOptimize(AssetOptimize, ABC):
......@@ -78,6 +78,14 @@ class FundDividendSortinoAssetOptimize(SortinoAssetOptimize):
self._datum = datum
self._conf = get_config(__name__)
@property
def annual_volatility_section(self):
return self._conf['annual-volatility-section']
@property
def annual_volatility_filter(self):
return self._conf['annual-volatility-filter']
@property
def asset_include(self):
return self._conf['asset-include']
......@@ -109,9 +117,15 @@ class FundDividendSortinoAssetOptimize(SortinoAssetOptimize):
sortino = sortino.T
sortino['score'] = sortino.apply(lambda r: sum([x['weight'] * r[x['name']] for x in self._config]), axis=1)
sortino.sort_values('score', ascending=False, inplace=True)
# todo 将记录入库
records = sortino.to_dict(orient='records')
data = {key: record for record in records for key in fund_ids}
self.save_sortino(day, data)
# 取得分数高的前optimize_count个
return pct_change.columns[sortino.index[0:self.optimize_count]].values,sortino['score']
return pct_change.columns[sortino.index[0:self.optimize_count]].values, sortino['score']
def save_sortino(self, day, datas):
for key, record in datas.items():
robo_indicator.update_sortino(key, day, json.dumps(record))
def get_optimize_pool(self, day):
opt_pool = rop.get_one(day=day, type=AssetPoolType.OPTIMIZE)
......@@ -133,6 +147,43 @@ class FundDividendSortinoAssetOptimize(SortinoAssetOptimize):
last_one = rop.get_last_one(day=day, type=AssetPoolType.OPTIMIZE)
return json.loads(last_one['asset_ids'])
def do_annual_volatility_filter(self, day, funds):
"""
年化波动率过滤器
@return:
"""
filtered = []
fund_ids = [fund['id'] for fund in funds]
pct_change = pd.DataFrame(self.get_pct_change(fund_ids, day))
pct_change.set_index('date', inplace=True)
ratio = annual_volatility(
pct_change.truncate(before=(day - relativedelta(**self.annual_volatility_section[0]))))
ratio = pd.Series(ratio).to_dict()
annual = {fund_id: value for fund_id in fund_ids for value in ratio.values()}
self.save_annual(day, annual)
filters = self.annual_volatility_filter
for f in filters:
customType = f.get('customType')
exclude = f.get('exclude')
volatility = f.get('volatility')
records = [fund for fund in funds if fund['customType'] == customType and fund['id'] in annual.keys()]
exclude = exclude if len(records) > exclude else len(records)
records = records[0:-exclude]
records = [record for record in records if annual.get(record['id']) > volatility]
filtered.extend(records)
return funds
def save_annual(self, day, annual):
datas = []
for key, record in annual.items():
data = {
"id": key,
"date": day,
"annual": record,
}
datas.append(data)
robo_indicator.insert(datas)
def get_filtered_funds(self, day):
funds = self._datum.get_datums(type=DatumType.FUND)
if get_config('portfolios.checker.month-fund-filter'):
......@@ -150,6 +201,7 @@ class FundDividendSortinoAssetOptimize(SortinoAssetOptimize):
if fund[filters] in self.asset_filter[filters]:
funds_in.append(fund)
return funds_in
funds = self.do_annual_volatility_filter(day, funds)
return funds
def get_groups(self, day=None):
......@@ -168,8 +220,9 @@ class FundDividendSortinoAssetOptimize(SortinoAssetOptimize):
def get_pct_change(self, fund_ids, day):
if not self._config:
raise exception(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])
days = [day - relativedelta(days=1, **dict_remove(x, ('weight', 'name'))) for x in self._config]
days.append(day - relativedelta(days=1, **self.annual_volatility_section[0]))
start = filter_weekend(sorted(days)[0])
fund_navs = pd.DataFrame(self._navs.get_fund_navs(fund_ids=tuple(fund_ids), min_date=start, max_date=day))
if not fund_navs.empty:
fund_navs.sort_values('nav_date', inplace=True)
......
......@@ -13,3 +13,13 @@ CREATE TABLE IF NOT EXISTS robo_assets_pool
AUTO_INCREMENT = 0
DEFAULT CHARSET = utf8mb4 COMMENT '资产池';
CREATE TABLE IF NOT EXISTS robo_indicator
(
`ri_rbd_id` bigint(20) NOT NULL,
`ri_date` datetime NOT NULL,
`ri_annual` double NOT NULL,
`ri_sortino` json NULL,
`ri_create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`ri_update_time` datetime NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
UNIQUE INDEX `ri_rbd_id`(`ri_rbd_id`, `ri_date`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
\ No newline at end of file
from py_jftech import write, mapper_columns
__COLUMNS__ = {
'ri_rbd_id': 'id',
'ri_date': 'date',
'ri_annual': 'annual',
'ri_sortino': 'sortino',
}
@write
def insert(datas):
datas = [mapper_columns(datas=x, columns=__COLUMNS__, ignore_none=False) for x in datas]
values = ','.join(
[f'''({','.join([(f"'{x[j]}'" if j in x and x[j] is not None else 'null') for j in __COLUMNS__.keys()])})''' for
x in datas])
return f'''insert into robo_indicator({','.join(__COLUMNS__.keys())}) values {values}'''
@write
def update_sortino(id, date, sortino):
return f'''update robo_indicator set ri_sortino='{sortino}' where ri_rbd_id={id} and ri_date='{date}' '''
@write
def clear():
return 'TRUNCATE robo_indicator'
......@@ -3,7 +3,7 @@ from datetime import datetime as dt
from py_jftech import component, autowired
from api import AssetPool, AssetOptimize
from asset_pool.dao import robo_assets_pool as rap
from asset_pool.dao import robo_assets_pool as rap, robo_indicator
@component
......@@ -18,3 +18,4 @@ class FundAssetPool(AssetPool):
def clear(self, day=None):
rap.delete(day)
robo_indicator.clear()
......@@ -78,6 +78,15 @@ asset-pool: # 资产池模块
weight: 0.2
asset-include: {'customType':[1,2,3,4]}
optimize-count: 3 #基金优选个数
annual-volatility-filter: #1各资产年化波动率末exclude位 2各资产年化波动率大于volatility
- customType: 1
exclude: 2
volatility: 0
- customType: 2
exclude: 1
volatility: 0
annual-volatility-section: # 波动率时间区间
- years: 3
portfolios: # 投组模块
holder: # 持仓投组相关
init-nav: 100 # 初始金额
......@@ -243,7 +252,7 @@ robo-executor: # 执行器相关
start-date: 2023-01-02 # 回测起始日期
end-date: 2023-10-31 # 回测截止日期
sealing-period: 10 #调仓封闭期
start-step: ${BACKTEST_START_STEP:2} # 回测从哪一步开始执行 1:计算资产池;2:计算最优投组:3:计算再平衡信号以及持仓投组
start-step: ${BACKTEST_START_STEP:1} # 回测从哪一步开始执行 1:计算资产池;2:计算最优投组:3:计算再平衡信号以及持仓投组
end-step: ${BACKTEST_END_STEP:3} # 回测从哪一步执行完成后结束执行 1:计算资产池;2:计算最优投组:3:计算再平衡信号以及持仓投组
clean-up: on
real: # 实盘执行器
......
......@@ -195,7 +195,8 @@ class DefaultSolver(Solver):
def solve_risk_parity(self):
model = self.create_model()
model.objective = Objective(expr=sum(
[(model.z[i] * model.w[i] * (self.risk_parity_sigma.iloc[i] @ model.w) - model.z[j] * model.w[j] * (self.risk_parity_sigma.iloc[j] @ model.w)) ** 2
[(model.z[i] * model.w[i] * (self.risk_parity_sigma.iloc[i] @ model.w) - model.z[j] * model.w[j] * (
self.risk_parity_sigma.iloc[j] @ model.w)) ** 2
for i in model.indices for j in model.indices]), sense=minimize)
self._solver.solve(model)
return self.calc_port_weight(model)
......@@ -451,7 +452,8 @@ class PRRSolver(ARCSolver):
model.cons_RR_LE_TRR = Constraint(
expr=sum([model.w[i] * RR_LE_TRR[i] for i in model.indices]) >= minRRweightWithinTRR)
# todo trr<指定值
# model.cons_LE_TRR = Constraint(expr=sum([model.w[i] * RR[i] for i in model.indices]) <= TRR)
if TRR < 5:
model.cons_RR_in_1_5 = Constraint(
expr=sum([model.z[i] * (RR_in_1_5[i] * self.max_count - RR_EQ_5[i]) for i in model.indices]) >= 0)
......
import pandas as pd
from py_jftech import autowired
from api import DatumType, Datum
def format_weight(weight: dict, to=1) -> dict:
@autowired
def format_weight(weight: dict, to=1, datum: Datum = None) -> dict:
"""
对权重的小数点进行截取,到指定权重
@param datum:
@param weight:
@param to: 指定权重
@return:
"""
# funds = datum.get_datums(type=DatumType.FUND)
# risk_dict = {fund['id']: fund['risk'] for fund in funds}
# risk = 0
# for k, v in weight.items():
# risk += risk_dict.get(int(k)) * v
# print(risk)
weight_series = pd.Series(weight)
weight_series = weight_series.fillna(0)
minidx = weight_series[weight_series > 0].idxmin()
maxidx = weight_series.idxmax()
weight_series = weight_series.apply(lambda x: round(x, 2))
if weight_series.sum() == to:
return dict(weight_series)
elif weight_series.sum() < to:
funds = datum.get_datums(type=DatumType.FUND)
risk_dict = {fund['id']: fund['risk'] for fund in funds}
id_sort = sorted(weight_series.to_dict().keys(), key=lambda x: risk_dict.get(int(x)))
# 低风险
minidx = id_sort[0]
# 高风险
maxidx = id_sort[-1]
if weight_series.sum() < to:
weight_series[minidx] += to - weight_series.sum()
elif weight_series.sum() > to:
weight_series[maxidx] += to - weight_series.sum()
......@@ -23,4 +39,4 @@ def format_weight(weight: dict, to=1) -> dict:
if __name__ == '__main__':
print(format_weight({19: 0.13, 27: 0.17, 56: 0.36}))
format_weight({"5": 0.35, "6": 0.35, "10": 0.09, "11": 0.16, "22": 0.05})
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