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
4c46b934
Commit
4c46b934
authored
Mar 13, 2023
by
wenwen.tang
😕
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
回测
parent
55b338de
Pipeline
#706
failed with stages
Changes
9
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
91 additions
and
14 deletions
+91
-14
api.py
api.py
+16
-0
robo_fund_navs.py
basic/dao/robo_fund_navs.py
+2
-0
config-svrobo5.yml
config-svrobo5.yml
+1
-2
builder.py
portfolios/builder.py
+6
-1
mysql.sql
portfolios/dao/mysql.sql
+3
-0
robo_hold_portfolios.py
portfolios/dao/robo_hold_portfolios.py
+2
-0
robo_mpt_portfolios.py
portfolios/dao/robo_mpt_portfolios.py
+1
-0
holder.py
portfolios/holder.py
+60
-10
robo_executor.py
robo_executor.py
+0
-1
No files found.
api.py
View file @
4c46b934
...
@@ -246,6 +246,14 @@ class PortfoliosBuilder(ABC):
...
@@ -246,6 +246,14 @@ class PortfoliosBuilder(ABC):
'''
'''
pass
pass
@
abstractmethod
def
get_all_portfolios
(
self
,
risk
:
PortfoliosRisk
=
None
):
"""
查询所有优选基金
@param risk:
"""
pass
class
Solver
(
ABC
):
class
Solver
(
ABC
):
'''
'''
...
@@ -424,6 +432,14 @@ class PortfoliosHolder(ABC):
...
@@ -424,6 +432,14 @@ class PortfoliosHolder(ABC):
'''
'''
pass
pass
@
property
@
abstractmethod
def
month_dividend
(
self
):
"""
获取当月配息
"""
pass
class
RoboExecutor
(
ABC
):
class
RoboExecutor
(
ABC
):
'''
'''
...
...
basic/dao/robo_fund_navs.py
View file @
4c46b934
...
@@ -4,6 +4,8 @@ __COLUMNS__ = {
...
@@ -4,6 +4,8 @@ __COLUMNS__ = {
'rfn_fund_id'
:
'fund_id'
,
'rfn_fund_id'
:
'fund_id'
,
'rfn_date'
:
'nav_date'
,
'rfn_date'
:
'nav_date'
,
'rfn_nav_cal'
:
'nav_cal'
,
'rfn_nav_cal'
:
'nav_cal'
,
'rfn_av'
:
'av'
,
'rfn_div'
:
'dividend'
,
}
}
__INSERT_COLUMNS__
=
{
__INSERT_COLUMNS__
=
{
...
...
config-svrobo5.yml
View file @
4c46b934
...
@@ -75,6 +75,7 @@ portfolios: # 投组模块
...
@@ -75,6 +75,7 @@ portfolios: # 投组模块
holder
:
# 持仓投组相关
holder
:
# 持仓投组相关
init-nav
:
100
# 初始金额
init-nav
:
100
# 初始金额
min-interval-days
:
10
# 两次实际调仓最小间隔期,单位交易日
min-interval-days
:
10
# 两次实际调仓最小间隔期,单位交易日
dividend-rate
:
0.09
#设定年化配息率
solver
:
# 解算器相关
solver
:
# 解算器相关
tol
:
1E-10
# 误差满足条件
tol
:
1E-10
# 误差满足条件
navs
:
# 净值要求
navs
:
# 净值要求
...
@@ -87,12 +88,10 @@ portfolios: # 投组模块
...
@@ -87,12 +88,10 @@ portfolios: # 投组模块
US_STOCK
:
[
0.3
,
0.5
,
0.7
]
US_STOCK
:
[
0.3
,
0.5
,
0.7
]
US_HY_BOND
:
[
0.6
,
0.4
,
0.2
]
US_HY_BOND
:
[
0.6
,
0.4
,
0.2
]
US_IG_BOND
:
[
0.1
,
0.1
,
0.1
]
US_IG_BOND
:
[
0.1
,
0.1
,
0.1
]
dividend-rate
:
0.09
riskctl-ratio
:
riskctl-ratio
:
US_STOCK
:
[
0.2
,
0.4
,
0.6
]
US_STOCK
:
[
0.2
,
0.4
,
0.6
]
US_HY_BOND
:
[
0.5
,
0.3
,
0.1
]
US_HY_BOND
:
[
0.5
,
0.3
,
0.1
]
US_IG_BOND
:
[
0.3
,
0.3
,
0.3
]
US_IG_BOND
:
[
0.3
,
0.3
,
0.3
]
dividend-rate
:
0.09
matrix-rtn-days
:
20
# 计算回报率矩阵时,回报率滚动天数
matrix-rtn-days
:
20
# 计算回报率矩阵时,回报率滚动天数
asset-count
:
[
1
,
3
]
# 投组资产个数。e.g. count 或 [min, max] 分别表示 最大最小都为count 或 最小为min 最大为max,另外这里也可以类似上面给不同风险等级分别配置
asset-count
:
[
1
,
3
]
# 投组资产个数。e.g. count 或 [min, max] 分别表示 最大最小都为count 或 最小为min 最大为max,另外这里也可以类似上面给不同风险等级分别配置
mpt
:
# mpt计算相关
mpt
:
# mpt计算相关
...
...
portfolios/builder.py
View file @
4c46b934
...
@@ -6,6 +6,7 @@ from pymysql import IntegrityError, constants
...
@@ -6,6 +6,7 @@ from pymysql import IntegrityError, constants
from
api
import
PortfoliosBuilder
,
PortfoliosRisk
,
AssetPool
,
Navs
,
PortfoliosType
,
Datum
,
SolveType
,
SolverFactory
from
api
import
PortfoliosBuilder
,
PortfoliosRisk
,
AssetPool
,
Navs
,
PortfoliosType
,
Datum
,
SolveType
,
SolverFactory
from
portfolios.dao
import
robo_mpt_portfolios
as
rmp
from
portfolios.dao
import
robo_mpt_portfolios
as
rmp
from
portfolios.dao.robo_mpt_portfolios
import
get_list
logger
=
logging
.
getLogger
(
__name__
)
logger
=
logging
.
getLogger
(
__name__
)
...
@@ -78,6 +79,9 @@ class MptPortfoliosBuilder(PortfoliosBuilder):
...
@@ -78,6 +79,9 @@ class MptPortfoliosBuilder(PortfoliosBuilder):
def
clear
(
self
,
day
=
None
,
risk
:
PortfoliosRisk
=
None
):
def
clear
(
self
,
day
=
None
,
risk
:
PortfoliosRisk
=
None
):
rmp
.
delete
(
min_date
=
day
,
risk
=
risk
)
rmp
.
delete
(
min_date
=
day
,
risk
=
risk
)
def
get_all_portfolios
(
self
,
risk
:
PortfoliosRisk
=
None
):
return
get_list
(
risk
=
risk
)
@
component
(
bean_name
=
'poem'
)
@
component
(
bean_name
=
'poem'
)
class
PoemPortfoliosBuilder
(
MptPortfoliosBuilder
):
class
PoemPortfoliosBuilder
(
MptPortfoliosBuilder
):
...
@@ -97,10 +101,11 @@ class PoemPortfoliosBuilder(MptPortfoliosBuilder):
...
@@ -97,10 +101,11 @@ class PoemPortfoliosBuilder(MptPortfoliosBuilder):
portfolio
,
cvar
=
solver
.
solve_poem
(
min_rtn
,
max_rtn
,
mpt_cvar
,
maxCVaR_whenMinV
)
portfolio
,
cvar
=
solver
.
solve_poem
(
min_rtn
,
max_rtn
,
mpt_cvar
,
maxCVaR_whenMinV
)
if
not
portfolio
:
if
not
portfolio
:
portfolio
=
mpt_portfolio
portfolio
=
mpt_portfolio
portfolios
=
{
**
portfolios
,
**
portfolio
}
portfolios
=
{
**
portfolios
,
**
portfolio
}
if
portfolios
:
if
portfolios
:
result
[
risk
]
=
{
result
[
risk
]
=
{
'solve'
:
SolveType
.
POEM
,
'solve'
:
SolveType
.
POEM
,
'portfolio'
:
json
.
dumps
(
portfolios
),
'portfolio'
:
json
.
dumps
(
portfolios
),
}
}
return
result
return
result
portfolios/dao/mysql.sql
View file @
4c46b934
...
@@ -27,6 +27,9 @@ CREATE TABLE IF NOT EXISTS robo_hold_portfolios
...
@@ -27,6 +27,9 @@ CREATE TABLE IF NOT EXISTS robo_hold_portfolios
rhp_rebalance
TINYINT
NOT
NULL
DEFAULT
0
COMMENT
'是否调仓'
,
rhp_rebalance
TINYINT
NOT
NULL
DEFAULT
0
COMMENT
'是否调仓'
,
rhp_portfolios
JSON
NOT
NULL
COMMENT
'投组信息'
,
rhp_portfolios
JSON
NOT
NULL
COMMENT
'投组信息'
,
rhp_nav
DOUBLE
(
12
,
4
)
NOT
NULL
COMMENT
'资产值'
,
rhp_nav
DOUBLE
(
12
,
4
)
NOT
NULL
COMMENT
'资产值'
,
`rhp_div`
double
(
12
,
4
)
NOT
NULL
COMMENT
'配息金额'
,
`rhp_div_acc`
double
(
12
,
4
)
NOT
NULL
COMMENT
'累计配息金额'
,
`v_nav_div_acc`
double
(
12
,
4
)
GENERATED
ALWAYS
AS
((
`rhp_div_acc`
+
`rhp_nav`
))
VIRTUAL
COMMENT
'配息金额+净值'
NOT
NULL
,
rhp_create_time
DATETIME
NOT
NULL
DEFAULT
CURRENT_TIMESTAMP
,
rhp_create_time
DATETIME
NOT
NULL
DEFAULT
CURRENT_TIMESTAMP
,
rhp_update_time
DATETIME
DEFAULT
NULL
ON
UPDATE
CURRENT_TIMESTAMP
,
rhp_update_time
DATETIME
DEFAULT
NULL
ON
UPDATE
CURRENT_TIMESTAMP
,
PRIMARY
KEY
(
rhp_id
),
PRIMARY
KEY
(
rhp_id
),
...
...
portfolios/dao/robo_hold_portfolios.py
View file @
4c46b934
...
@@ -6,6 +6,8 @@ __COLUMNS__ = {
...
@@ -6,6 +6,8 @@ __COLUMNS__ = {
'rhp_id'
:
'id'
,
'rhp_id'
:
'id'
,
'rhp_date'
:
'date'
,
'rhp_date'
:
'date'
,
'rhp_risk'
:
'risk'
,
'rhp_risk'
:
'risk'
,
'rhp_div'
:
'dividend'
,
'rhp_div_acc'
:
'div_acc'
,
'rhp_rrs_id'
:
'signal_id'
,
'rhp_rrs_id'
:
'signal_id'
,
'rhp_rebalance'
:
'rebalance'
,
'rhp_rebalance'
:
'rebalance'
,
'rhp_portfolios'
:
'portfolios'
,
'rhp_portfolios'
:
'portfolios'
,
...
...
portfolios/dao/robo_mpt_portfolios.py
View file @
4c46b934
...
@@ -49,4 +49,5 @@ def get_list(max_date=None, min_date=None, type: PortfoliosType = None, risk: Po
...
@@ -49,4 +49,5 @@ def get_list(max_date=None, min_date=None, type: PortfoliosType = None, risk: Po
return
f
'''
return
f
'''
select {','.join([f"{x[0]} as {x[1]}" for x in __COLUMNS__.items()])} from robo_mpt_portfolios
select {','.join([f"{x[0]} as {x[1]}" for x in __COLUMNS__.items()])} from robo_mpt_portfolios
{where(*sqls, rmp_risk=risk, rmp_type=type)}
{where(*sqls, rmp_risk=risk, rmp_type=type)}
order by rmp_date
'''
'''
portfolios/holder.py
View file @
4c46b934
...
@@ -6,7 +6,7 @@ from py_jftech import (
...
@@ -6,7 +6,7 @@ from py_jftech import (
component
,
autowired
,
get_config
,
next_workday
,
format_date
component
,
autowired
,
get_config
,
next_workday
,
format_date
)
)
from
api
import
PortfoliosHolder
,
PortfoliosRisk
,
Navs
,
RoboExecutor
,
PortfoliosType
from
api
import
PortfoliosHolder
,
PortfoliosRisk
,
Navs
,
RoboExecutor
,
PortfoliosType
,
PortfoliosBuilder
from
portfolios.dao
import
robo_hold_portfolios
as
rhp
from
portfolios.dao
import
robo_hold_portfolios
as
rhp
from
portfolios.utils
import
format_weight
from
portfolios.utils
import
format_weight
...
@@ -17,9 +17,10 @@ logger = logging.getLogger(__name__)
...
@@ -17,9 +17,10 @@ logger = logging.getLogger(__name__)
class
DividendPortfoliosHolder
(
PortfoliosHolder
):
class
DividendPortfoliosHolder
(
PortfoliosHolder
):
@
autowired
(
names
=
{
'executor'
:
RoboExecutor
.
use_name
()})
@
autowired
(
names
=
{
'executor'
:
RoboExecutor
.
use_name
()})
def
__init__
(
self
,
navs
:
Navs
=
None
,
executor
:
RoboExecutor
=
None
):
def
__init__
(
self
,
navs
:
Navs
=
None
,
executor
:
RoboExecutor
=
None
,
builder
:
PortfoliosBuilder
=
None
):
self
.
_navs
=
navs
self
.
_navs
=
navs
self
.
_executor
=
executor
self
.
_executor
=
executor
self
.
_builder
=
builder
self
.
_config
=
get_config
(
__name__
)
self
.
_config
=
get_config
(
__name__
)
def
get_portfolio_type
(
self
,
day
,
risk
:
PortfoliosRisk
)
->
PortfoliosType
:
def
get_portfolio_type
(
self
,
day
,
risk
:
PortfoliosRisk
)
->
PortfoliosType
:
...
@@ -42,27 +43,70 @@ class DividendPortfoliosHolder(PortfoliosHolder):
...
@@ -42,27 +43,70 @@ class DividendPortfoliosHolder(PortfoliosHolder):
def
build_hold_portfolio
(
self
,
day
,
risk
:
PortfoliosRisk
):
def
build_hold_portfolio
(
self
,
day
,
risk
:
PortfoliosRisk
):
last_nav
=
rhp
.
get_last_one
(
max_date
=
day
,
risk
=
risk
)
last_nav
=
rhp
.
get_last_one
(
max_date
=
day
,
risk
=
risk
)
start
=
next_workday
(
last_nav
[
'date'
]
if
last_nav
else
self
.
_executor
.
start_date
)
# 从基金优选池选取所有调仓日基金
portfolios
=
self
.
_builder
.
get_all_portfolios
(
risk
)
portfoliosMap
=
{
p
[
'date'
]:
p
[
'portfolio'
]
for
p
in
portfolios
}
start
=
last_nav
[
'date'
]
if
last_nav
else
list
(
portfoliosMap
.
keys
())[
0
]
try
:
try
:
if
not
last_nav
:
pass
while
start
<=
day
:
while
start
<=
day
:
logger
.
info
(
f
"start to build hold portfolio[{risk.name}] for date[{format_date(start)}]"
)
logger
.
info
(
f
"start to build hold portfolio[{risk.name}] for date[{format_date(start)}]"
)
self
.
no_rebalance
(
start
,
risk
,
last_nav
)
if
start
in
portfoliosMap
.
keys
():
self
.
do_rebalance
(
start
,
risk
,
portfoliosMap
[
start
],
last_nav
)
else
:
self
.
no_rebalance
(
start
,
risk
,
last_nav
)
start
=
next_workday
(
start
)
start
=
next_workday
(
start
)
last_nav
=
rhp
.
get_last_one
(
max_date
=
day
,
risk
=
risk
)
last_nav
=
rhp
.
get_last_one
(
max_date
=
day
,
risk
=
risk
)
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
exception
(
f
"build hold portfolio[{risk.name}] for date[{format_date(start)}] failure."
,
e
)
logger
.
exception
(
f
"build hold portfolio[{risk.name}] for date[{format_date(start)}] failure."
,
e
)
def
do_rebalance
(
self
,
day
,
risk
:
PortfoliosRisk
,
portfolio
,
last_nav
):
weight
=
{
int
(
x
[
0
]):
x
[
1
]
for
x
in
json
.
loads
(
portfolio
)
.
items
()}
dividend_acc
=
0
if
last_nav
:
share
=
{
int
(
x
):
y
for
x
,
y
in
json
.
loads
(
last_nav
[
'portfolios'
])[
'share'
]
.
items
()}
fund_div_tuple
=
self
.
get_navs_and_div
(
fund_ids
=
tuple
(
set
(
weight
)
|
set
(
share
)),
day
=
day
)
navs
=
fund_div_tuple
[
0
]
fund_dividend
=
fund_div_tuple
[
1
]
nav
=
round
(
sum
([
navs
[
x
]
*
y
for
x
,
y
in
share
.
items
()]),
4
)
dividend_acc
=
last_nav
[
'div_acc'
]
else
:
nav
=
self
.
init_nav
fund_div_tuple
=
self
.
get_navs_and_div
(
fund_ids
=
tuple
(
weight
),
day
=
day
)
navs
=
fund_div_tuple
[
0
]
fund_dividend
=
fund_div_tuple
[
1
]
dividend
=
nav
*
self
.
month_dividend
nav
=
nav
-
dividend
share
=
{
x
:
nav
*
w
/
navs
[
x
]
for
x
,
w
in
weight
.
items
()}
fund_dividend
=
sum
(
map
(
lambda
k
:
share
[
k
]
*
fund_dividend
[
k
],
filter
(
lambda
k
:
k
in
fund_dividend
,
share
.
keys
())))
dividend_acc
=
dividend
+
dividend_acc
+
fund_dividend
rhp
.
insert
({
'date'
:
day
,
'risk'
:
risk
,
'dividend'
:
dividend
,
'div_acc'
:
dividend_acc
,
'rebalance'
:
True
,
'portfolios'
:
{
'weight'
:
weight
,
'share'
:
share
,
},
'nav'
:
nav
,
})
def
no_rebalance
(
self
,
day
,
risk
:
PortfoliosRisk
,
last_nav
):
def
no_rebalance
(
self
,
day
,
risk
:
PortfoliosRisk
,
last_nav
):
share
=
{
int
(
x
):
y
for
x
,
y
in
json
.
loads
(
last_nav
[
'portfolios'
])[
'share'
]
.
items
()}
share
=
{
int
(
x
):
y
for
x
,
y
in
json
.
loads
(
last_nav
[
'portfolios'
])[
'share'
]
.
items
()}
navs
=
self
.
get_navs
(
fund_ids
=
tuple
(
share
),
day
=
day
)
fund_div_tuple
=
self
.
get_navs_and_div
(
fund_ids
=
tuple
(
share
),
day
=
day
)
navs
=
fund_div_tuple
[
0
]
fund_dividend
=
fund_div_tuple
[
1
]
nav
=
round
(
sum
([
navs
[
x
]
*
y
for
x
,
y
in
share
.
items
()]),
4
)
nav
=
round
(
sum
([
navs
[
x
]
*
y
for
x
,
y
in
share
.
items
()]),
4
)
weight
=
{
x
:
round
(
y
*
navs
[
x
]
/
nav
,
2
)
for
x
,
y
in
share
.
items
()}
weight
=
{
x
:
round
(
y
*
navs
[
x
]
/
nav
,
2
)
for
x
,
y
in
share
.
items
()}
weight
=
format_weight
(
weight
)
weight
=
format_weight
(
weight
)
fund_dividend
=
sum
(
map
(
lambda
k
:
share
[
k
]
*
fund_dividend
[
k
],
filter
(
lambda
k
:
k
in
fund_dividend
,
share
.
keys
())))
dividend_acc
=
last_nav
[
'div_acc'
]
+
fund_dividend
rhp
.
insert
({
rhp
.
insert
({
'date'
:
day
,
'date'
:
day
,
'risk'
:
risk
,
'risk'
:
risk
,
'dividend'
:
0
,
'div_acc'
:
dividend_acc
,
'signal_id'
:
last_nav
[
'signal_id'
],
'signal_id'
:
last_nav
[
'signal_id'
],
'rebalance'
:
False
,
'rebalance'
:
False
,
'portfolios'
:
{
'portfolios'
:
{
...
@@ -72,15 +116,21 @@ class DividendPortfoliosHolder(PortfoliosHolder):
...
@@ -72,15 +116,21 @@ class DividendPortfoliosHolder(PortfoliosHolder):
'nav'
:
nav
,
'nav'
:
nav
,
})
})
def
get_navs
(
self
,
day
,
fund_ids
):
def
get_navs
_and_div
(
self
,
day
,
fund_ids
):
navs
=
pd
.
DataFrame
(
self
.
_navs
.
get_fund_navs
(
fund_ids
=
fund_ids
,
max_date
=
day
))
navs
=
pd
.
DataFrame
(
self
.
_navs
.
get_fund_navs
(
fund_ids
=
fund_ids
,
max_date
=
day
))
navs
=
navs
.
pivot_table
(
index
=
'nav_date'
,
columns
=
'fund_id'
,
values
=
'nav_cal'
)
dividend
=
navs
.
pivot_table
(
index
=
'nav_date'
,
columns
=
'fund_id'
,
values
=
'dividend'
)
navs
=
navs
.
pivot_table
(
index
=
'nav_date'
,
columns
=
'fund_id'
,
values
=
'av'
)
navs
.
fillna
(
method
=
'ffill'
,
inplace
=
True
)
navs
.
fillna
(
method
=
'ffill'
,
inplace
=
True
)
return
dict
(
navs
.
iloc
[
-
1
])
dividend
.
fillna
(
method
=
'ffill'
,
inplace
=
True
)
return
dict
(
navs
.
iloc
[
-
1
]),
dict
(
dividend
.
iloc
[
-
1
])
def
clear
(
self
,
day
=
None
,
risk
:
PortfoliosRisk
=
None
):
def
clear
(
self
,
day
=
None
,
risk
:
PortfoliosRisk
=
None
):
rhp
.
delete
(
min_date
=
day
,
risk
=
risk
)
rhp
.
delete
(
min_date
=
day
,
risk
=
risk
)
@
property
def
month_dividend
(
self
):
return
self
.
_config
[
'dividend-rate'
]
/
12
@
property
@
property
def
interval_days
(
self
):
def
interval_days
(
self
):
return
self
.
_config
[
'min-interval-days'
]
return
self
.
_config
[
'min-interval-days'
]
...
...
robo_executor.py
View file @
4c46b934
...
@@ -119,7 +119,6 @@ class BacktestExecutor(RoboExecutor):
...
@@ -119,7 +119,6 @@ class BacktestExecutor(RoboExecutor):
now
=
dt
.
now
()
now
=
dt
.
now
()
wait
([
self
.
async_build_hold
(
x
)
for
x
in
PortfoliosRisk
])
wait
([
self
.
async_build_hold
(
x
)
for
x
in
PortfoliosRisk
])
logger
.
info
(
f
"build hold portfolios success, use[{(dt.now() - now).seconds}s]"
)
logger
.
info
(
f
"build hold portfolios success, use[{(dt.now() - now).seconds}s]"
)
logger
.
info
(
"start to export report"
.
center
(
50
,
'-'
))
@
asynchronized
(
isolate
=
True
)
@
asynchronized
(
isolate
=
True
)
def
async_build_risk_date
(
self
,
asset_id
):
def
async_build_risk_date
(
self
,
asset_id
):
...
...
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