Commit 3615cbcd authored by wenwen.tang's avatar wenwen.tang 😕

robo for hk

parent 4c68be65
......@@ -77,10 +77,15 @@ class FundDividendSortinoAssetOptimize(SortinoAssetOptimize):
self._navs = navs
self._datum = datum
self._conf = get_config(__name__)
@property
def asset_include(self):
return self._conf['asset-include']
@property
def asset_filter(self):
return self._conf.get('asset-filter')
@property
def optimize_count(self):
return self._conf['optimize-count']
......@@ -127,11 +132,23 @@ class FundDividendSortinoAssetOptimize(SortinoAssetOptimize):
last_one = rop.get_last_one(day=day, type=AssetPoolType.OPTIMIZE)
return json.loads(last_one['asset_ids'])
def get_filtered_funds(self):
funds = self._datum.get_datums(type=DatumType.FUND)
if self.asset_filter:
filters = list(self.asset_filter.keys())[0]
funds_in = []
for fund in funds:
if fund[filters] in self.asset_filter[filters]:
funds_in.append(fund)
return funds_in
return funds
def get_groups(self):
funds = pd.DataFrame(self._datum.get_datums(type=DatumType.FUND))
funds = pd.DataFrame(self.get_filtered_funds())
result = []
for (category, asset_type), fund_group in funds.groupby(by=['category', 'assetType']):
if category in self.asset_include:
include = list(self.asset_include.keys())[0]
for key, fund_group in funds.groupby(by=include):
if key in self.asset_include[include]:
result.append(tuple(fund_group['id']))
return result
......
......@@ -8,7 +8,7 @@ from api import DatumType, Datum, PortfoliosRisk, RoboExecutor
from basic.dao import robo_base_datum as rbd
@component
@component(bean_name='datum')
class DefaultDatum(Datum):
def __init__(self):
......@@ -79,3 +79,16 @@ class DefaultDatum(Datum):
}))
return True
return False
@component(bean_name='datum')
class HkDatum(DefaultDatum):
def get_datums(self, type: DatumType = None, crncy=None, risk=None, datum_ids=None, ticker=None, exclude=True):
datas = super().get_datums(type, crncy, None, datum_ids, ticker, exclude)
# 香港基金risk与现有risk有差异,全查出来再筛选
datas = [d for d in datas if d.get('hk')]
# 进行值的覆盖操作
datas = [d.update(d['hk']) or d for d in datas]
# 进行筛选操作
datas = [d for d in datas if not risk or d['risk'] == risk]
return datas
py-jftech:
logger:
version: 1
formatters:
brief:
format: "%(asctime)s - %(levelname)s - %(message)s"
simple:
format: "%(asctime)s - %(filename)s - %(levelname)s - %(message)s"
handlers:
console:
class: logging.StreamHandler
formatter: simple
level: DEBUG
stream: ext://sys.stdout
file:
class: logging.handlers.TimedRotatingFileHandler
level: INFO
formatter: brief
filename: ${LOG_FILE:logs/info.log}
interval: 1
backupCount: 30
encoding: utf8
when: D
# loggers:
# basic.sync:
# level: DEBUG
# handlers: [console]
# propagate: no
root:
level: ${LOG_LEVEL:INFO}
handlers: ${LOG_HANDLERS:[ console ]}
database:
host: ${MYSQL_HOST:192.168.68.85}
port: ${MYSQL_PORT:3306}
user: ${MYSQL_USER:root}
password: ${MYSQL_PWD:changeit}
dbname: ${MYSQL_DBNAME:jftech_robo}
injectable:
names:
backtest: robo_executor.BacktestExecutor
datum: basic.datum.HkDatum
hold-report: portfolios.holder.DivHoldReportor
mpt: portfolios.builder.PoemPortfoliosBuilder
# email:
# server: smtphz.qiye.163.com
# user: jft-ra@thizgroup.com
# password: 5dbb#30ec6d3
mulit-process:
max-workers: ${MAX_PROCESS:4}
basic: # 基础信息模块
sync:
start-date: 2007-01-01 # 同步数据开始日期
datum: # 资料模块
change:
date: ${DATUM_CHANGE_DATE}
file: ${DATUM_CHANGE_FILE}
excludes: # 排除的资料彭博ticker
backtest: []
real: []
# navs: # 净值模块
# exrate: # 汇率,如果不开启,整个这块注释掉
# - from: EUR # 需要转换的货币类型
# ticker: EURUSD BGN Curncy # 汇率值的彭博ticker
asset-pool: # 资产池模块
asset-optimize: # 资产优选模块
sortino-weight: # sortino计算需要的权重,下面每一条为一次计算,e.g. months: 3, weight: 0.5 表示 3个月数据使用权重0.5来计算分值
- months: 3
weight: 0.5
- months: 6
weight: 0.3
- years: 1
weight: 0.2
asset-include: {'riskLevel':['LOW','HIGH']} #基金按照字典key分类,并筛选出value中的值,与下面normal-ratio设置的值相对应
asset-filter: {'risk':[1,2,3,4,5]} #基金按照字典key分类,并筛选出value中的值,仅作为筛选
optimize-count: 3 #基金优选个数
portfolios: # 投组模块
holder: # 持仓投组相关
init-nav: 100000 # 初始金额
min-interval-days: 10 # 两次实际调仓最小间隔期,单位交易日
dividend-rate: 0 #设定年化配息率
dividend-drift-rate: 0.1 #超过基准配息率上下10%触发配息率重置
dividend-date: 15 #配息日,每月15号
dividend-adjust-day: [] #每年的首个季度调整配息
warehouse-frequency: 3 #每隔3个月调一次仓
solver: # 解算器相关
tol: 1E-10 # 误差满足条件
navs: # 净值要求
range: # 需要净值数据的区间, days: 90 表示90自然日,months: 3 表示3个自然月
days: 90
max-nan: # 最大缺失净值条件
asset: 8 # 单一资产最多缺少多少交易日数据,则踢出资产池
day: 0.5 # 单一交易日最多缺少百分之多少净值,则删除该交易日
normal-ratio: #分别对应低中高风险所占比率
LOW: [ 0.5, 0.5, 0.7 ]
HIGH: [ 0.5, 0.4, 0.2 ]
riskctl-ratio:
LOW: [ 0.5, 0.5, 0.7 ]
HIGH: [ 0.5, 0.4, 0.2 ]
matrix-rtn-days: 20 # 计算回报率矩阵时,回报率滚动天数
asset-count: [3,3] # 投组资产个数。e.g. count 或 [min, max] 分别表示 最大最小都为count 或 最小为min 最大为max,另外这里也可以类似上面给不同风险等级分别配置
mpt: # mpt计算相关
cvar-beta: 0.2 # 计算Kbeta 需要用到
quantile: 0.9 # 分位点,也可以给不同风险等级分别配置
low-weight: 0.05 # 最低权重
# high-weight: [ 1 ] # 最高权重比例,可给一个值,也可以给多个值,当多个值时,第一个表示只有一个资产时权重,第二个表示只有两个资产时权重,以此类推,最后一个表示其他资产个数时的权重
poem: # poem相关
cvar-scale-factor: 0.1 # 计算时用到的系数
reports: # 报告模块相关
navs:
type: FUND
tickers:
- TEMTECI LX Equity
- TEPLX US Equity
- FRDPX US Equity
- FKRCX US Equity
- FTNRACU LX Equity
benchmark: # benchmark报告
ft:
init-amount: 100 # 初始金额
stock-rate: # stock型基金比例
RR3: 0.3
RR4: 0.5
RR5: 0.7
fixed-range: # 固定区间收益率
range-dates: # 固定起始截止日期
- start: 2008-01-01
end: 2008-10-27
- start: 2011-05-02
end: 2011-10-04
- start: 2013-05-08
end: 2013-06-24
- start: 2014-09-03
end: 2014-12-16
- start: 2015-04-28
end: 2016-01-21
- start: 2018-01-26
end: 2018-10-29
- start: 2020-01-20
end: 2020-03-23
relative-range: # 相对区间收益率
range-dates: # 相对时间周期
- days: 1
name: '一天'
- weeks: 1
name: '一周'
- months: 1
name: '一月'
- months: 3
name: '三月'
- months: 6
name: '六月'
- years: 1
name: '一年'
- years: 2
name: '两年'
- years: 3
name: '三年'
- years: 5
name: '五年'
- years: 10
name: '十年'
- dates: ~
name: '成立以来'
exports:
backtest: # 回测导出曹策略
save-path: ${EXPORT_PATH:excels} # 导出报告文件存放路径,如果以./或者../开头,则会以执行python文件为根目录,如果以/开头,则为系统绝对路径,否则,以项目目录为根目录
file-name: ${EXPORT_FILENAME:real} # 导出报告的文件名
save-config: ${EXPORT_CONFIG:off} # 是否保存配置文件
include-report: # 需要导出的报告类型列表,下面的顺序,也代表了excel中sheet的顺序
# - funds-report # 基金资料
# - navs-report # 净值报告
- hold-report # 持仓报告
- signal-report # 信号报告
- benckmark-report # benckmark报告
- combo-report # 持仓对比
- indicators-report # 各种特殊指标报告
- fixed-range-report # 固定区间收益报告
- relative-range-report # 相对区间收益报告
- year-range-report # 单年区间业绩报告
- month-div-rate-report # 月度配息率比较
- year-div-rate-report # 年度配息率比较
real-daily:
file-name: svROBO5_portfolios
include-report:
- daily-hold-report
- daily-signal-report
email:
receives:
- wenwen.tang@thizgroup.com
copies: ${DAILY_EMAIL_COPIES}
subject:
default: "ROBO5_TAIBEI-实盘版-每日投組推薦_{today}"
rebalance: "ROBO5_TAIBEI-实盘版-每日投組推薦_{today}_今日有調倉信號!!!"
content:
default: "Dear All: 附件是今天生成的推薦組合,請驗收,謝謝! 注>:該郵件為自動發送,如有問題請聯繫矽谷團隊 telan_qian@chifufund.com"
rebalance: "Dear All: 附件是今天生成的推薦組合以及調倉信號,請驗收,謝謝! 注>:該郵件為自動發送,如有問題請聯繫矽谷團隊 telan_qian@chifufund.com"
daily-monitor:
file-name: svROBO5_monitor
include-report:
- name: relative-range-report # 相对区间收益报告
min-date: ~
- name: contribution-report # 贡献率报告
min-date: {days: 30}
- name: high-weight-report # 高风险资产占比
min-date: {days: 30}
- name: asset-pool-report # 基金池
min-date: {days: 30}
- name: combo-report # 持仓报告
min-date: {days: 40}
- name: mpt-report
min-date: {days: 30}
- name: signal-report
min-date: ~
- name: crisis-one-report
min-date: {days: 30}
- name: crisis-two-report
min-date: {days: 30}
- name: market-right-report
min-date: {days: 30}
- name: drift-buy-report
min-date: {days: 30}
email:
receives:
- wenwen.tang@thizgroup.com
copies: ${MONITOR_EMAIL_COPIES}
subject: "SVROBO5-实盘版-每日监测_{today}"
content: "Dear All: 附件是今天生成的监测数据,請驗收,謝謝! 注>:該郵件為自動發送,如有問題請聯繫矽谷團隊 telan_qian@chifufund.com"
robo-executor: # 执行器相关
use: ${ROBO_EXECUTOR:backtest} # 执行哪个执行器,优先取系统环境变量ROBO_EXECUTOR的值,默认backtest
sync-data: ${SYNC_DATA:off} # 是否开启同步资料数据
backtest: # 回测执行器相关
start-date: 2012-10-16 # 回测起始日期
end-date: 2023-03-01 # 回测截止日期
sealing-period: 10 #调仓封闭期
start-step: ${BACKTEST_START_STEP:1} # 回测从哪一步开始执行 1:计算资产池;2:计算最优投组:3:计算再平衡信号以及持仓投组
end-step: ${BACKTEST_END_STEP:3} # 回测从哪一步执行完成后结束执行 1:计算资产池;2:计算最优投组:3:计算再平衡信号以及持仓投组
clean-up: true
real: # 实盘执行器
start-date: 2022-09-01 # 实盘开始时间
......@@ -30,14 +30,15 @@ py-jftech:
level: ${LOG_LEVEL:INFO}
handlers: ${LOG_HANDLERS:[ console ]}
database:
host: ${MYSQL_HOST:::1}
host: ${MYSQL_HOST:192.168.68.85}
port: ${MYSQL_PORT:3306}
user: ${MYSQL_USER:root}
password: ${MYSQL_PWD:changeit}
dbname: ${MYSQL_DBNAME:jftech_robo}
injectable:
name:
names:
backtest: robo_executor.BacktestExecutor
datum: basic.datum.DefaultDatum
hold-report: portfolios.holder.DivHoldReportor
mpt: portfolios.builder.PoemPortfoliosBuilder
# email:
......@@ -71,7 +72,7 @@ asset-pool: # 资产池模块
weight: 0.3
- years: 1
weight: 0.2
asset-include: ['US_STOCK','US_IG_BOND','US_HY_BOND']
asset-include: {'category':['US_STOCK','US_IG_BOND','US_HY_BOND']}
optimize-count: 3 #基金优选个数
portfolios: # 投组模块
holder: # 持仓投组相关
......@@ -81,6 +82,7 @@ portfolios: # 投组模块
dividend-drift-rate: 0.1 #超过基准配息率上下10%触发配息率重置
dividend-date: 15 #配息日,每月15号
dividend-adjust-day: [1,4,7,10] #每年的首个季度调整配息
warehouse-frequency: 1 #每隔1个月调一次仓
solver: # 解算器相关
tol: 1E-10 # 误差满足条件
navs: # 净值要求
......
......@@ -8,7 +8,8 @@ from py_jftech import (
component, autowired, get_config, next_workday, format_date, is_workday
)
from api import PortfoliosHolder, PortfoliosRisk, Navs, RoboExecutor, PortfoliosType, PortfoliosBuilder, RoboReportor
from api import PortfoliosHolder, PortfoliosRisk, Navs, RoboExecutor, PortfoliosType, PortfoliosBuilder, RoboReportor, \
DatumType, Datum
from portfolios.dao import robo_hold_portfolios as rhp
from portfolios.utils import format_weight
......@@ -19,12 +20,14 @@ logger = logging.getLogger(__name__)
class DividendPortfoliosHolder(PortfoliosHolder):
@autowired(names={'executor': RoboExecutor.use_name()})
def __init__(self, navs: Navs = None, executor: RoboExecutor = None, builder: PortfoliosBuilder = None):
def __init__(self, navs: Navs = None, executor: RoboExecutor = None, builder: PortfoliosBuilder = None,
datum: Datum = None):
self._navs = navs
self._executor = executor
self._builder = builder
self._config = get_config(__name__)
self._last_div = None
self._datum = datum
def get_portfolio_type(self, day, risk: PortfoliosRisk) -> PortfoliosType:
return PortfoliosType.NORMAL
......@@ -78,9 +81,10 @@ class DividendPortfoliosHolder(PortfoliosHolder):
# 每年的首个季度调整配息
if day.month in self._config.get('dividend-adjust-day'):
asset_nav = last_nav['asset_nav']
# 配息率
div_rate = self._last_div * 12 / asset_nav
# 超过基准配息率上下10%触发配息率重置
if abs((self._config['dividend-rate'] - div_rate) / self._config['dividend-rate']) > \
# 年配息率减去配息率差值超过基准配息率上下10%触发配息率重置
if self.month_dividend > 0 and abs((self._config['dividend-rate'] - div_rate) / self._config['dividend-rate']) > \
self._config['dividend-drift-rate']:
# 以本月前一天的单位净值进行配息计算
dividend = last_nav['asset_nav'] * self.month_dividend
......@@ -99,6 +103,7 @@ class DividendPortfoliosHolder(PortfoliosHolder):
dividend_acc = self._last_div + dividend_acc
asset_nav = fund_av + fund_dividend + dividend
nav = last_nav['nav'] * asset_nav / last_nav['asset_nav']
share = {x: fund_av * w / navs[x] for x, w in weight.items()}
else:
fund_av = self.init_nav
fund_div_tuple = self.get_navs_and_div(fund_ids=tuple(weight), day=day)
......@@ -109,7 +114,12 @@ class DividendPortfoliosHolder(PortfoliosHolder):
dividend_acc = dividend + dividend_acc
nav = self.init_nav
asset_nav = fund_av + fund_dividend + dividend
share = {x: fund_av * w / navs[x] for x, w in weight.items()}
funds = self._datum.get_datums(type=DatumType.FUND)
funds_subscription_rate = {fund['id']: fund.get('subscriptionRate', 0) for fund in funds}
share = {x: (1-funds_subscription_rate[x])*(fund_av * w) / navs[x] for x, w in weight.items()}
# 初始买入扣手续费
fee = sum(funds_subscription_rate[x]*(fund_av * w) for x, w in weight.items())
fund_av = fund_av - fee
rhp.insert({
'date': day,
'risk': risk,
......
......@@ -202,7 +202,7 @@ class DefaultSolver(Solver):
return sum(port_r_hist[0: self.k_beta]) / self.k_beta
def get_weight(self):
# todo 根据self.risk找配置
# 根据asset-include中的对应key找配置
return self.transfer_type[self.category][0]
def create_model(self):
......@@ -227,7 +227,8 @@ class DefaultSolver(Solver):
def reset_navs(self, day):
asset_ids = self._assets.get_pool(day)
datum = self._datum.get_datums(type=DatumType.FUND, datum_ids=asset_ids)
asset_ids_group = {k: [d['id'] for d in datum if d['category'] == k] for k in set(d['category'] for d in datum)}
category = list(get_config('asset-pool')['asset-optimize']['asset-include'].keys())[0]
asset_ids_group = {k: [d['id'] for d in datum if d[category] == k] for k in set(d[category] for d in datum)}
navs_group = {}
for category, asset_ids in asset_ids_group.items():
min_date = day - relativedelta(**self.get_config('navs.range'))
......
......@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
@component(bean_name='backtest')
class BacktestExecutor(RoboExecutor):
@autowired(names={'optimize': 'dividend'})
@autowired(names={'optimize': 'dividend', 'datum': 'hk-datum'})
def __init__(self, datum: Datum = None, pool: AssetPool = None,
syncs: List[DataSync] = None, export: RoboExportor = None,
builder: PortfoliosBuilder = None, hold: PortfoliosHolder = None):
......@@ -37,21 +37,22 @@ class BacktestExecutor(RoboExecutor):
def get_first_business_day(start_date, end_date):
# 生成日期范围并转换为DataFrame
dates = pd.date_range(start_date, end_date, freq='MS')
dates = dates.insert(0, start_date)
df = pd.DataFrame({'dates': dates})
# 提取每个月的第一个工作日
df['first_business_day'] = df['dates'].apply(
lambda x: pd.date_range(start=x, end=x + pd.offsets.MonthEnd(0), freq='B')[0]
)
# 返回第一个工作日列表
result = list(df['first_business_day'])
delta = workday_range(start_date, result[0])
# 每隔n个月提取第一个工作日
result = []
for i in range(0, len(df), get_config('portfolios')['holder']['warehouse-frequency']):
result.append(df.iloc[i]['first_business_day'])
delta = workday_range(result[0], result[1])
period = get_config(__name__)['backtest']['sealing-period']
if len(delta) <= period:
result.pop(0)
result.insert(0, start_date)
result.pop(1)
return result
@property
def start_date(self):
return pd.to_datetime(filter_weekend(self._config['start-date']))
......
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