Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
R
robo-dividend
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
wenwen.tang
robo-dividend
Commits
34ac0de5
Commit
34ac0de5
authored
Apr 02, 2024
by
wenwen.tang
😕
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
https://www.tapd.cn/59187493/documents/show/1159187493001000730
依照文档进行模型优化
parent
5b121a27
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
133 additions
and
15 deletions
+133
-15
asset_optimize.py
asset_pool/asset_optimize.py
+59
-6
mysql.sql
asset_pool/dao/mysql.sql
+10
-0
robo_indicator.py
asset_pool/dao/robo_indicator.py
+27
-0
pool.py
asset_pool/pool.py
+2
-1
config-svrobo_Mdiv_PRR3.yml
config-svrobo_Mdiv_PRR3.yml
+10
-1
solver.py
portfolios/solver.py
+4
-2
utils.py
portfolios/utils.py
+21
-5
No files found.
asset_pool/asset_optimize.py
View file @
34ac0de5
...
...
@@ -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
)
...
...
asset_pool/dao/mysql.sql
View file @
34ac0de5
...
...
@@ -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
asset_pool/dao/robo_indicator.py
0 → 100644
View file @
34ac0de5
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'
asset_pool/pool.py
View file @
34ac0de5
...
...
@@ -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
()
config-svrobo_Mdiv_PRR3.yml
View file @
34ac0de5
...
...
@@ -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
:
# 实盘执行器
...
...
portfolios/solver.py
View file @
34ac0de5
...
...
@@ -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
)
...
...
portfolios/utils.py
View file @
34ac0de5
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
}
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment