Commit 3d3e4fde authored by jichao's avatar jichao

实盘完成

parent c42d36f3
......@@ -99,7 +99,7 @@ class Datum(ABC):
'''
@abstractmethod
def get_datums(self, type: DatumType = None, crncy=None, risk=None, datum_ids=None, ticker=None):
def get_datums(self, type: DatumType = None, crncy=None, risk=None, datum_ids=None, ticker=None, exclude=True):
'''
获取资料信息,当id和ticker都有时,取二者并集
:param type: 资料类型
......@@ -107,6 +107,7 @@ class Datum(ABC):
:param risk: 风险等级,仅对基金资料有效
:param datum_ids: 资料ID列表
:param ticker: 资料ticker列表
:param exclude: 是否排除过滤的资料,默认为True
:return: 资料信息数据
'''
pass
......@@ -120,6 +121,15 @@ class Datum(ABC):
'''
pass
@abstractmethod
def update_change(self, date):
'''
在指定对日期,执行资料变更,如果有变更则返回True, 否则返回False,注意,改方法非幂等,变更后无法复原
:param date: 执行变更的日期
:return: 如果有变更则返回True,否则返回False
'''
pass
class Navs(ABC):
'''
......
......@@ -43,9 +43,12 @@ class SortinoAssetOptimize(AssetOptimize, ABC):
return pct_change.columns[sortino.index[0]]
def get_optimize_pool(self, day):
opt_pool = rop.get_one(day=day, type=AssetPoolType.OPTIMIZE)
if opt_pool is not None:
return json.loads(opt_pool['asset_ids'])
last_one = rop.get_last_one(day=day, type=AssetPoolType.OPTIMIZE)
start = get_quarter_start(day or dt.today())
if not last_one or start > last_one['date'] or self.has_incept_asset(last_one['date'] + timedelta(1), day):
if not last_one or start > last_one['date'] or self.has_incept_asset(last_one['date'] + timedelta(1), day) or self.has_change(day):
pool = []
min_dates = self.nav_min_dates
max_incept_date = sorted([(day - relativedelta(**x)) for x in self.delta_kwargs])[0]
......@@ -86,6 +89,10 @@ class SortinoAssetOptimize(AssetOptimize, ABC):
'''
pass
@abstractmethod
def has_change(self, day):
return False
@component
class FundSortinoAssetOptimize(SortinoAssetOptimize):
......@@ -108,6 +115,9 @@ class FundSortinoAssetOptimize(SortinoAssetOptimize):
end_date = sorted([(end_date - relativedelta(**x)) for x in self.delta_kwargs])[0]
return len([x for x in self.nav_min_dates.items() if start_date <= x[1] <= end_date]) > 0
def has_change(self, day):
return self._datum.update_change(day)
def get_groups(self):
funds = pd.DataFrame(self._datum.get_datums(type=DatumType.FUND))
min_dates = self._navs.get_nav_start_date()
......
from py_jftech import read, where, to_tuple
from py_jftech import read, where, to_tuple, write
from api import DatumType
......@@ -13,3 +13,8 @@ def get_base_datums(type: DatumType = None, crncy=None, risk=None, datum_ids=Non
'v_rbd_bloomberg_ticker': to_tuple(ticker)
}
return f'''select rbd_id as id, rbd_datas as datas from robo_base_datum {where(**kwargs)}'''
@write
def update_datum(id, datas: str):
return f'''update robo_base_datum set rbd_datas = '{datas}' where rbd_id = {id}'''
import json
import os
from datetime import datetime as dt
from typing import List
import pandas as pd
from py_jftech import component, parse_date, get_config, to_tuple, autowired
from py_jftech import component, parse_date, get_config, to_tuple, autowired, get_project_path, transaction
from api import DatumType, Datum, PortfoliosRisk, RoboReportor
from api import DatumType, Datum, PortfoliosRisk, RoboReportor, RoboExecutor
from basic.dao import robo_base_datum as rbd
......@@ -17,7 +18,27 @@ class DefaultDatum(Datum):
@property
def excludes(self):
return self._config['excludes'] if 'excludes' in self._config else []
excludes = self._config['excludes'] if 'excludes' in self._config else []
if isinstance(excludes, dict):
excludes = excludes[RoboExecutor.use_name()] if RoboExecutor.use_name() in excludes else []
return excludes
@property
def change_date(self):
change = self._config['change'] if 'change' in self._config else {}
return pd.to_datetime(change['date']) if 'date' in change else None
@property
def change_file(self):
change = self._config['change'] if 'change' in self._config else {}
if 'file' not in change or change['file'] is None:
return None
file_path: str = change['file']
if file_path.startswith('.'):
return os.path.abspath(os.path.join(os.path.dirname(__file__), file_path))
elif file_path.startswith('/'):
return os.path.abspath(file_path)
return os.path.abspath(os.path.join(get_project_path(), file_path))
def format_datum(self, datum):
if DatumType(datum['type']) is DatumType.FUND:
......@@ -27,14 +48,14 @@ class DefaultDatum(Datum):
}
return datum
def get_datums(self, type: DatumType = None, crncy=None, risk=None, datum_ids=None, ticker=None):
def get_datums(self, type: DatumType = None, crncy=None, risk=None, datum_ids=None, ticker=None, exclude=True):
datum_ids = to_tuple(datum_ids)
if ticker:
datums = rbd.get_base_datums(type=type, ticker=ticker)
datum_ids = tuple(set(datum_ids or []) | {x['id'] for x in datums})
result = rbd.get_base_datums(type=type, crncy=crncy, risk=risk, datum_ids=datum_ids)
result = [{**json.loads(x['datas']), 'id': x['id']} for x in result]
return [self.format_datum(x) for x in result if DatumType(x['type']) is not DatumType.FUND or x['bloombergTicker'] not in self.excludes]
return [self.format_datum(x) for x in result if not exclude or x['bloombergTicker'] not in self.excludes]
def get_high_risk_datums(self, risk: PortfoliosRisk):
risk3 = self.get_datums(type=DatumType.FUND, risk=3)
......@@ -47,6 +68,19 @@ class DefaultDatum(Datum):
return risk3 + self.get_datums(type=DatumType.FUND, risk=(4, 5))
return None
@transaction
def update_change(self, date):
if self.change_date is not None and self.change_date == pd.to_datetime(date):
if self.change_file is not None:
for fund in pd.read_excel(self.change_file).to_dict('records'):
db_data = rbd.get_base_datums(ticker=fund['bloombergTicker'])
rbd.update_datum(db_data['id'], json.dumps({
**json.loads(db_data['datas']),
**fund
}))
return True
return False
@component(bean_name='funds-report')
class FundReportor(RoboReportor):
......@@ -62,5 +96,6 @@ class FundReportor(RoboReportor):
def load_report(self, max_date=dt.today(), min_date=None) -> List[dict]:
datums = self._datum.get_datums(type=DatumType.FUND)
datums = pd.DataFrame(datums)
datums = datums[['id', 'ftTicker', 'bloombergTicker', 'chineseName', 'englishName', 'lipperKey', 'isin', 'currency', 'risk', 'inceptDate', 'category', 'assetType']]
datums = datums[
['id', 'ftTicker', 'bloombergTicker', 'chineseName', 'englishName', 'lipperKey', 'isin', 'currency', 'risk', 'inceptDate', 'category', 'assetType']]
return datums.to_dict('records')
......@@ -2,7 +2,6 @@ import logging
import unittest
from typing import List
import pandas as pd
from py_jftech import autowired, parse_date, to_str
from api import Navs, Datum, PortfoliosRisk, DataSync, RoboReportor
......
......@@ -35,6 +35,12 @@ py-jftech:
user: ${MYSQL_USER:root}
password: ${MYSQL_PWD:123456}
dbname: ${MYSQL_DBNAME:jftech_robo}
database-2:
host: 106.14.56.221
port: 3306
user: robo_user
password: robo2.1@20220521
dbname: robo_ft_fund_real
injectable:
types:
api.PortfoliosBuilder: portfolios.builder.PoemPortfoliosBuilder
......@@ -48,15 +54,34 @@ basic: # 基础信息模块
sync:
start-date: 2007-01-01 # 同步数据开始日期
datum: # 资料模块
change:
date: ${DATUM_CHANGE_DATE}
file: ${DATUM_CHANGE_FILE}
excludes: # 排除的资料彭博ticker
- 'FKUQX US Equity'
- 'FTAAUSH LX Equity'
- 'FTJAPAU LX Equity'
- 'TEGAUH1 LX Equity'
- 'TMEEAAU LX Equity'
- 'TEUSAAU LX Equity'
- 'FTEAUH1 LX Equity'
- 'TFIAAUS LX Equity'
backtest:
- 'FKUQX US Equity'
- 'FTAAUSH LX Equity'
- 'FTJAPAU LX Equity'
- 'TEGAUH1 LX Equity'
- 'TMEEAAU LX Equity'
- 'TEUSAAU LX Equity'
- 'FTEAUH1 LX Equity'
- 'TFIAAUS LX Equity'
real:
- 'FGFSACU LX Equity'
- 'TMEEAAU LX Equity'
- 'FTEAMUH LX Equity'
- 'FKUTX US Equity'
- 'TEMUSGI LX Equity'
- 'TEMFIAI LX Equity'
- 'TEMGROA LX Equity'
- 'TEMFMEA LX Equity'
- 'TEMDGAA LX Equity'
- 'TEMJAAU LX Equity'
- 'TEMFHAC LX Equity'
- 'TEMLATA LX Equity'
- 'LEPEUAA ID Equity'
- 'LGBOAAU ID Equity'
navs: # 净值模块
exrate: # 汇率,如果不开启,整个这块注释掉
- from: EUR # 需要转换的货币类型
......@@ -139,7 +164,7 @@ rebalance: # 再平衡模块
signals: # 信号相关
crisis-signal: # 危机信号相关
exp-years: 3 # 预警期时长,单位自然年,点到点计算
exp-init: 2008-01-01 # 设置起始危机预警开始时间,如果关闭初始预警起,注释到这一条即可
exp-init: 2022-03-04 # 设置起始危机预警开始时间,如果关闭初始预警起,注释到这一条即可
inversion-years: 1 # 利率倒挂计算时长,单位自然年,点到点取值
inversion-threshold: 0.3 # 利率倒挂触发阀值
crisis-1: # 危机1相关
......@@ -211,7 +236,7 @@ reports: # 报告模块相关
backtest: # 回测导出曹策略
exist-build: on # 如果报告文件存在,是否重新构建文件
save-path: ${EXPORT_PATH:excels} # 导出报告文件存放路径,如果以./或者../开头,则会以执行python文件为根目录,如果以/开头,则为系统绝对路径,否则,以项目目录为根目录
file-name: ${EXPORT_FILENAME:1323}
file-name: ${EXPORT_FILENAME:real}
include-report: # 需要导出的报告类型列表,下面的顺序,也代表了excel中sheet的顺序
# - funds-report # 基金资料
# - navs-report # 净值报告
......@@ -224,16 +249,16 @@ reports: # 报告模块相关
- fixed-range-report # 固定区间收益报告
- relative-range-report # 相对区间收益报告
robo-executor: # 执行器相关
use: ${ROBO_EXECUTOR:backtest} # 执行哪个执行器,优先取系统环境变量ROBO_EXECUTOR的值,默认backtest
use: ${ROBO_EXECUTOR:real} # 执行哪个执行器,优先取系统环境变量ROBO_EXECUTOR的值,默认backtest
sync-data: ${SYNC_DATA:off} # 是否开启同步资料数据
backtest: # 回测执行器相关
start-date: 2008-01-02 # 回测起始日期
end-date: 2022-11-01 # 回测截止日期
start-date: 2022-09-01 # 回测起始日期
end-date: 2023-02-20 # 回测截止日期
start-step: ${BACKTEST_START_STEP:4} # 回测从哪一步开始执行 1:计算资产ewma;2:计算资产池;3:计算最优投组:4:计算再平衡信号以及持仓投组
end-step: ${BACKTEST_END_STEP:4} # 回测从哪一步执行完成后结束执行 1:计算资产ewma;2:计算资产池;3:计算最优投组:4:计算再平衡信号以及持仓投组
clean-up: on
clean-up: off
real: # 实盘执行器
start-date: 2022-11-01 # 实盘开始时间
start-date: 2022-09-01 # 实盘开始时间
include-date:
- 2023-02-18
- 2023-03-25
......
......@@ -29,7 +29,7 @@ class PortfoliosTest(unittest.TestCase):
@autowired(names={'hold': 'next-re'})
def test_build_hold(self, hold: PortfoliosHolder = None):
hold.build_hold_portfolio(parse_date('2022-11-01'), PortfoliosRisk.FT9)
hold.build_hold_portfolio(parse_date('2023-02-23'), PortfoliosRisk.FT9)
@autowired(names={'reportor': 'hold-report'})
def test_hold_report(self, reportor: RoboReportor = None):
......
import logging
import unittest
from dateutil.relativedelta import relativedelta
from dateutil.relativedelta import relativedelta
from py_jftech import autowired, parse_date, to_str, next_workday
from api import RebalanceSignal, PortfoliosRisk, RebalanceRuler, RoboReportor
......
......@@ -174,18 +174,17 @@ class RealExecutor(RoboExecutor):
for sync in self._syncs:
sync.do_sync()
date = self.curt_date
for date in pd.date_range(start='2008-01-17', end='2008-01-20'):
if is_workday(date) or date in self.include_date:
date = prev_workday(date)
for risk in PortfoliosRisk:
logger.info(f"start to build risk[{risk.name}] real for date[{format_date(date)}]".center(50, '-'))
now = dt.now()
# 因为每天都必须有NORMAL最优投组,不管用不用
self._builder.get_portfolios(date, risk)
self._hold.build_hold_portfolio(date, risk)
self._ruler.take_next_signal(date, risk)
# 如果当前持仓为风控投组,则还要计算风控投组,不管用不用
p_type = self._hold.get_portfolio_type(date, risk)
if p_type is not PortfoliosType.NORMAL:
self._builder.get_portfolios(date, risk, type=p_type)
logger.info(f"build risk[{risk.name}] real for date[{format_date(date)}] success, use[{(dt.now() - now).seconds}s]")
if is_workday(date) or date in self.include_date:
date = prev_workday(date)
for risk in PortfoliosRisk:
logger.info(f"start to build risk[{risk.name}] real for date[{format_date(date)}]".center(50, '-'))
now = dt.now()
# 因为每天都必须有NORMAL最优投组,不管用不用
self._builder.get_portfolios(date, risk)
self._hold.build_hold_portfolio(date, risk)
self._ruler.take_next_signal(date, risk)
# 如果当前持仓为风控投组,则还要计算风控投组,不管用不用
p_type = self._hold.get_portfolio_type(date, risk)
if p_type is not PortfoliosType.NORMAL:
self._builder.get_portfolios(date, risk, type=p_type)
logger.info(f"build risk[{risk.name}] real for date[{format_date(date)}] success, use[{(dt.now() - now).seconds}s]")
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