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
495d15b0
Commit
495d15b0
authored
Nov 18, 2022
by
纪超
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
完成日志、配置文件、数据库工具模块
parent
db33dba7
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
218 additions
and
118 deletions
+218
-118
api.py
api.py
+12
-3
asset_optimize.py
asset_pool/asset_optimize.py
+7
-4
asset_pool.py
asset_pool/asset_pool.py
+4
-3
asset_risk.py
asset_pool/asset_risk.py
+8
-5
asset_ewma_value.py
asset_pool/dao/asset_ewma_value.py
+1
-2
asset_risk_dates.py
asset_pool/dao/asset_risk_dates.py
+1
-2
robo_assets_pool.py
asset_pool/dao/robo_assets_pool.py
+2
-1
robo_fund_navs.py
basic/dao/robo_fund_navs.py
+0
-1
datum.py
basic/datum.py
+1
-0
navs.py
basic/navs.py
+3
-5
config.yml
config.yml
+7
-5
__init__.py
framework/__init__.py
+2
-2
env_config.py
framework/env_config.py
+4
-4
logs.py
framework/logs.py
+12
-6
mulit_process.py
framework/mulit_process.py
+1
-1
main.py
main.py
+11
-12
builder.py
portfolios/builder.py
+124
-60
robo_mpt_portfolios.py
portfolios/dao/robo_mpt_portfolios.py
+3
-2
test.py
test.py
+15
-0
No files found.
api.py
View file @
495d15b0
from
framework
import
parse_date
,
get_quarter_start
,
get_config
from
abc
import
ABC
,
abstractmethod
from
enum
import
Enum
,
unique
from
dateutil.relativedelta
import
relativedelta
@
unique
...
...
@@ -158,9 +156,20 @@ class PortfoliosBuilder(ABC):
@
abstractmethod
def
get_portfolios
(
self
,
day
,
risk
:
PortfoliosRisk
,
type
:
PortfoliosType
=
PortfoliosType
.
NORMAL
):
'''
获取指定日期,指定风险等级的投资组合
获取指定日期,指定风险等级,指定类型的投资组合
:param type: 投组的类型
:param day: 指定日期
:param risk: 风险等级
:return: 资产组合字典{id: weight}
'''
pass
@
abstractmethod
def
build_portfolio
(
self
,
day
,
type
:
PortfoliosType
):
'''
构建指定日期,指定类型的投资组合
:param day: 指定日期
:param type: 指定类型
:return 投资组合数据{risk: {...}},计算明细数据 {...}
'''
pass
asset_pool/asset_optimize.py
View file @
495d15b0
import
json
import
pandas
as
pd
from
abc
import
ABC
,
abstractmethod
from
datetime
import
datetime
as
dt
import
pandas
as
pd
from
dateutil.relativedelta
import
relativedelta
from
empyrical
import
sortino_ratio
from
framework
import
filter_weekend
,
dict_remove
,
get_config
,
component
,
autowired
,
get_quarter_start
from
api
import
AssetOptimize
,
Navs
,
BusinessException
,
Datum
,
AssetPoolType
from
asset_pool.dao
import
robo_assets_pool
as
rop
from
datetime
import
datetime
as
d
t
from
framework
import
filter_weekend
,
dict_remove
,
get_config
,
component
,
autowired
,
get_quarter_star
t
class
SortinoAssetOptimize
(
AssetOptimize
,
ABC
):
...
...
@@ -88,7 +90,8 @@ class FundSortinoAssetOptimize(SortinoAssetOptimize):
def
get_pct_change
(
self
,
fund_ids
,
day
):
if
not
self
.
_config
:
raise
BusinessException
(
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
])
start
=
filter_weekend
(
sorted
([
day
-
relativedelta
(
days
=
1
,
**
dict_remove
(
x
,
(
'weight'
,
'name'
)))
for
x
in
self
.
_config
])[
0
])
fund_navs
=
pd
.
DataFrame
(
self
.
_navs
.
get_fund_navs
(
fund_ids
=
tuple
(
fund_ids
),
min_date
=
start
,
max_date
=
day
))
fund_navs
.
sort_values
(
'nav_date'
,
inplace
=
True
)
fund_navs
=
fund_navs
.
pivot_table
(
index
=
'nav_date'
,
columns
=
'fund_id'
,
values
=
'nav_cal'
)
...
...
asset_pool/asset_pool.py
View file @
495d15b0
from
framework
import
component
,
autowired
from
api
import
AssetPool
,
AssetOptimize
,
AssetRisk
from
datetime
import
datetime
as
dt
from
api
import
AssetPool
,
AssetOptimize
,
AssetRisk
from
framework
import
component
,
autowired
@
component
class
FundAssetPool
(
AssetPool
):
...
...
@@ -14,4 +15,4 @@ class FundAssetPool(AssetPool):
def
get_pool
(
self
,
day
=
dt
.
today
()):
opti_pool
=
self
.
_optimize
.
get_optimize_pool
(
day
)
risk_pool
=
self
.
_risk
.
get_risk_pool
(
day
)
return
[
x
for
x
in
opti_pool
if
x
not
in
risk_pool
]
\ No newline at end of file
return
[
x
for
x
in
opti_pool
if
x
not
in
risk_pool
]
asset_pool/asset_risk.py
View file @
495d15b0
import
json
import
logging
from
datetime
import
datetime
as
dt
import
pandas
as
pd
...
...
@@ -7,7 +8,9 @@ from scipy.stats import norm
from
api
import
AssetRisk
,
Navs
,
AssetRiskDateType
as
DateType
,
Datum
,
AssetPoolType
from
asset_pool.dao
import
asset_risk_dates
as
ard
,
asset_ewma_value
as
aev
,
robo_assets_pool
as
rap
from
framework
import
component
,
autowired
,
get_config
,
log
,
format_date
,
block_execute
from
framework
import
component
,
autowired
,
get_config
,
format_date
,
block_execute
logger
=
logging
.
getLogger
(
__name__
)
def
get_risk_start_date
():
...
...
@@ -38,7 +41,6 @@ class CvarEwmaAssetRisk(AssetRisk):
return
json
.
loads
(
asset_pool
[
'asset_ids'
])
def
is_risk
(
self
,
id
,
day
)
->
bool
:
print
(
id
,
day
)
asset_pool
=
rap
.
get_one
(
day
,
AssetPoolType
.
RISK
)
if
asset_pool
:
return
id
in
json
.
loads
(
asset_pool
[
'asset_ids'
])
...
...
@@ -49,11 +51,11 @@ class CvarEwmaAssetRisk(AssetRisk):
def
build_risk_date
(
self
,
asset_id
,
day
=
dt
.
today
()):
risk_date
=
not
None
try
:
log
.
debug
(
f
"start build risk date for asset[{asset_id}] to date[{format_date(day)}]"
)
log
ger
.
info
(
f
"start build risk date for asset[{asset_id}] to date[{format_date(day)}]"
)
while
risk_date
is
not
None
:
risk_date
=
self
.
get_next_date
(
asset_id
,
day
=
day
)
except
Exception
as
e
:
log
.
exception
(
f
"build risk date for asset[{asset_id}] after date[{risk_date}] error"
,
e
)
log
ger
.
exception
(
f
"build risk date for asset[{asset_id}] after date[{risk_date}] error"
,
e
)
def
get_next_date
(
self
,
asset_id
,
day
=
dt
.
today
()):
last
=
ard
.
get_last_one
(
asset_id
,
day
)
...
...
@@ -83,7 +85,8 @@ class CvarEwmaAssetRisk(AssetRisk):
if
row
[
'rtn'
]
<
rtns
[
rtns
.
date
==
cvar_start_date
]
.
iloc
[
0
]
.
rtn
:
# 当日回报率跌破最低点, 则直接触发
tigger
=
True
elif
row
[
'rtn'
]
<=
self
.
_config
[
'cvar'
][
'threshold'
]
and
len
(
cvar_rtns
)
>=
self
.
_config
[
'cvar'
][
'min-volume'
]:
elif
row
[
'rtn'
]
<=
self
.
_config
[
'cvar'
][
'threshold'
]
and
len
(
cvar_rtns
)
>=
self
.
_config
[
'cvar'
][
'min-volume'
]:
# 当日回报率小于等于阀值并且有足够cvar累计计算数据,则计算cvar判断
alpha
=
1
-
self
.
_config
[
'cvar'
][
'coef'
]
mean
=
cvar_rtns
[
'rtn'
]
.
mean
()
...
...
asset_pool/dao/asset_ewma_value.py
View file @
495d15b0
from
framework
import
read
,
write
,
where
,
format_date
__COLUMNS__
=
{
'aev_id'
:
'id'
,
'aev_date'
:
'date'
,
...
...
@@ -30,7 +29,7 @@ def get_last_one(asset_id, max_date=None):
@
read
def
get_list
(
asset_id
,
min_date
=
None
,
max_date
=
None
):
sqls
=
[]
sqls
=
[]
if
min_date
:
sqls
.
append
(
f
"aev_date >= '{format_date(min_date)}'"
)
if
max_date
:
...
...
asset_pool/dao/asset_risk_dates.py
View file @
495d15b0
from
framework
import
read
,
write
,
where
,
format_date
from
api
import
AssetRiskDateType
as
DateType
from
framework
import
read
,
write
,
where
,
format_date
__COLUMNS__
=
{
'ard_id'
:
'id'
,
...
...
@@ -28,4 +28,3 @@ def get_last_one(fund_id, date, type: DateType = None):
select {','.join([f"`{x[0]}` as `{x[1]}`" for x in __COLUMNS__.items()])}
from asset_risk_dates {where(sql, **kwargs)} order by ard_date desc, ard_type asc limit 1
'''
asset_pool/dao/robo_assets_pool.py
View file @
495d15b0
import
json
from
framework
import
read
,
write
,
where
,
format_date
from
api
import
AssetPoolType
from
framework
import
read
,
write
,
where
,
format_date
__COLUMNS__
=
{
'rap_id'
:
'id'
,
...
...
basic/dao/robo_fund_navs.py
View file @
495d15b0
from
framework
import
read
,
where
,
format_date
,
to_tuple
__COLUMNS__
=
{
'rfn_fund_id'
:
'fund_id'
,
'rfn_date'
:
'nav_date'
,
...
...
basic/datum.py
View file @
495d15b0
import
json
from
api
import
DatumType
,
Datum
from
basic.dao
import
robo_base_datum
as
rbd
from
framework
import
component
,
parse_date
...
...
basic/navs.py
View file @
495d15b0
import
pandas
as
pd
from
api
import
Navs
,
Datum
from
basic.dao
import
robo_exrate
as
re
,
robo_fund_navs
as
rfn
from
framework
import
get_config
,
component
,
autowired
...
...
@@ -18,7 +19,8 @@ class DefaultNavs(Navs):
navs
=
pd
.
DataFrame
(
navs
)
navs
=
navs
.
pivot_table
(
index
=
'nav_date'
,
columns
=
'fund_id'
,
values
=
'nav_cal'
)
for
exrate_config
in
self
.
_config
[
'exrate'
]:
exrate
=
pd
.
DataFrame
(
re
.
get_exrates
(
ticker
=
exrate_config
[
'ticker'
],
min_date
=
navs
.
index
.
min
(),
max_date
=
navs
.
index
.
max
()))
exrate
=
pd
.
DataFrame
(
re
.
get_exrates
(
ticker
=
exrate_config
[
'ticker'
],
min_date
=
navs
.
index
.
min
(),
max_date
=
navs
.
index
.
max
()))
exrate
=
exrate
[[
'date'
,
'close'
]]
exrate
.
set_index
(
'date'
,
inplace
=
True
)
for
fund
in
self
.
_datum
.
get_fund_datums
(
crncy
=
exrate_config
[
'from'
]):
...
...
@@ -30,7 +32,3 @@ class DefaultNavs(Navs):
navs
.
sort_values
(
by
=
[
'fund_id'
,
'nav_date'
],
inplace
=
True
)
navs
=
navs
.
to_dict
(
'records'
)
return
navs
if
__name__
==
'__main__'
:
print
(
isinstance
((),
tuple
))
config.yml
View file @
495d15b0
...
...
@@ -16,10 +16,10 @@ framework:
user
:
jft-ra@thizgroup.com
password
:
5dbb#30ec6d3
mulit-process
:
max-workers
:
4
max-workers
:
8
logger
:
version
:
1
use
:
${LOG_NAME:root}
use
:
prod
formatters
:
brief
:
format
:
"
%(asctime)s
-
%(levelname)s
-
%(message)s"
...
...
@@ -29,7 +29,7 @@ framework:
console
:
class
:
logging.StreamHandler
formatter
:
simple
level
:
INFO
level
:
DEBUG
stream
:
ext://sys.stdout
file
:
class
:
logging.handlers.TimedRotatingFileHandler
...
...
@@ -42,9 +42,11 @@ framework:
when
:
D
loggers
:
prod
:
handlers
:
[
console
,
file
]
handlers
:
[
console
,
file
]
level
:
INFO
propagate
:
0
propagate
:
no
portfolios
:
level
:
DEBUG
root
:
level
:
INFO
handlers
:
[
console
]
...
...
framework/__init__.py
View file @
495d15b0
...
...
@@ -2,9 +2,9 @@ from .date_utils import *
from
.base
import
*
from
.database
import
read
,
write
,
transaction
,
where
,
to_columns
from
.env_config
import
config
,
get_config
from
.log
ger
import
build_logger
,
logger
as
log
from
.log
s
import
build_logger
,
get_logger
from
.injectable
import
component
,
autowired
,
get_instance
,
init_injectable
as
_init_injectable
from
.mulit_process
import
process_pool
,
create_process_pool
,
block_execute
_init_injectable
()
del
injectable
,
log
ger
,
env_config
,
database
,
base
,
date_utils
,
_init_injectable
,
mulit_process
del
injectable
,
log
s
,
env_config
,
database
,
base
,
date_utils
,
_init_injectable
,
mulit_process
framework/env_config.py
View file @
495d15b0
...
...
@@ -4,7 +4,7 @@ from functools import partial
import
yaml
from
.base
import
*
from
framework
.base
import
*
has_regex_module
=
False
ENV_VAR_MATCHER
=
re
.
compile
(
...
...
@@ -17,7 +17,6 @@ ENV_VAR_MATCHER = re.compile(
"""
,
re
.
VERBOSE
)
IMPLICIT_ENV_VAR_MATCHER
=
re
.
compile
(
r"""
.* # matches any number of any characters
...
...
@@ -27,7 +26,6 @@ IMPLICIT_ENV_VAR_MATCHER = re.compile(
"""
,
re
.
VERBOSE
)
RECURSIVE_ENV_VAR_MATCHER
=
re
.
compile
(
r"""
\$\{ # match characters `${` literally
...
...
@@ -89,7 +87,9 @@ yaml.add_implicit_resolver(
config
=
build_config
()
def
get_config
(
module
:
str
=
None
):
def
get_config
(
module
:
str
=
None
,
file
:
str
=
None
):
if
module
==
'__main__'
:
module
=
file
result
=
config
if
module
:
for
name
in
[
x
.
replace
(
'_'
,
'-'
)
for
x
in
module
.
split
(
'.'
)]:
...
...
framework/log
ger
.py
→
framework/log
s
.py
View file @
495d15b0
import
logging
import
os
from
logging
import
config
as
cf
,
getLogger
from
framework.env_config
import
get_config
from
logging
import
config
as
cf
from
framework.base
import
get_project_path
from
framework.env_config
import
get_config
def
build_logger
(
config
,
name
=
'root'
):
def
build_logger
(
config
):
if
'handlers'
in
config
and
'file'
in
config
[
'handlers'
]:
file
=
config
[
'handlers'
][
'file'
]
path
=
os
.
path
.
join
(
get_project_path
(),
file
[
"filename"
])
os
.
makedirs
(
os
.
path
.
split
(
path
)[
0
],
exist_ok
=
True
)
file
[
"filename"
]
=
os
.
path
.
abspath
(
path
)
cf
.
dictConfig
(
config
)
return
getLogger
(
name
)
config
=
get_config
(
__name__
)
logger
=
build_logger
(
config
,
name
=
config
[
'use'
]
if
'use'
in
config
else
None
)
if
config
else
None
config
=
get_config
(
"framework.logger"
)
if
config
:
build_logger
(
config
)
def
get_logger
(
name
=
None
):
return
logging
.
getLogger
(
config
[
'use'
]
if
'use'
in
config
and
config
[
'use'
]
is
not
None
else
name
)
framework/mulit_process.py
View file @
495d15b0
from
concurrent.futures
import
ProcessPoolExecutor
,
as_completed
from
framework.env_config
import
get_config
from
functools
import
partial
,
wraps
config
=
get_config
(
__name__
)
process_pool
=
ProcessPoolExecutor
(
max_workers
=
config
[
'max-workers'
]
or
2
)
...
...
main.py
View file @
495d15b0
from
framework
import
autowired
,
parse_date
,
log
from
framework
import
autowired
,
parse_date
,
get_logger
from
api
import
PortfoliosBuilder
,
PortfoliosRisk
logger
=
get_logger
(
'main'
)
@
autowired
@
autowired
(
names
=
{
'builder'
:
'poem'
})
def
start
(
builder
:
PortfoliosBuilder
=
None
):
day
=
parse_date
(
'2022-11-07'
)
log
.
info
(
builder
.
get_portfolios
(
day
,
PortfoliosRisk
.
FT3
))
def
test
(
arg
):
if
arg
:
return
1
,
1
else
:
return
None
logger
.
info
(
builder
.
get_portfolios
(
day
,
PortfoliosRisk
.
FT3
))
if
__name__
==
'__main__'
:
log
.
info
(
"start"
)
start
()
\ No newline at end of file
logger
.
info
(
"info"
)
logger
.
debug
(
'debug'
)
logger
.
warning
(
'warning'
)
logger
.
error
(
'error'
)
logger
.
critical
(
'critical'
)
# start()
portfolios/builder.py
View file @
495d15b0
import
json
import
os
import
sys
import
json
import
pandas
as
pd
from
dateutil.relativedelta
import
relativedelta
...
...
@@ -8,9 +8,11 @@ from numpy import NAN
from
pyomo.environ
import
*
from
api
import
PortfoliosBuilder
,
PortfoliosRisk
,
AssetPool
,
Navs
,
PortfoliosType
,
Datum
,
SolveType
from
framework
import
component
,
autowired
,
get_config
from
framework
import
component
,
autowired
,
get_config
,
format_date
,
get_logger
from
portfolios.dao
import
robo_mpt_portfolios
as
rmp
logger
=
get_logger
(
__name__
)
def
create_solver
():
if
sys
.
platform
.
find
(
'win'
)
==
0
:
...
...
@@ -24,7 +26,8 @@ def create_solver():
class
MptSolver
:
@
autowired
def
__init__
(
self
,
risk
:
PortfoliosRisk
,
type
:
PortfoliosType
,
assets
:
AssetPool
=
None
,
navs
:
Navs
=
None
,
datum
:
Datum
=
None
):
def
__init__
(
self
,
risk
:
PortfoliosRisk
,
type
:
PortfoliosType
,
assets
:
AssetPool
=
None
,
navs
:
Navs
=
None
,
datum
:
Datum
=
None
):
self
.
__navs
=
None
self
.
risk
=
risk
self
.
type
=
type
or
PortfoliosType
.
NORMAL
...
...
@@ -59,17 +62,32 @@ class MptSolver:
result
=
self
.
rtn_matrix
*
12
return
result
.
values
@
property
def
beta
(
self
):
return
self
.
get_config
(
'mpt.cvar-beta'
)
@
property
def
k_beta
(
self
):
return
round
(
len
(
self
.
rtn_history
)
*
self
.
get_config
(
'mpt.cvar-beta'
)
+
0.499999
)
return
round
(
len
(
self
.
rtn_history
)
*
self
.
beta
+
0.499999
)
@
property
def
pct_value
(
self
):
return
self
.
get_config
(
'mpt.quantile'
)
def
solve_max_rtn
(
self
):
model
=
self
.
create_model
()
model
.
objective
=
Objective
(
expr
=
sum
([
model
.
w
[
i
]
*
self
.
rtn_annualized
[
i
]
for
i
in
model
.
indices
]),
sense
=
maximize
)
model
.
objective
=
Objective
(
expr
=
sum
([
model
.
w
[
i
]
*
self
.
rtn_annualized
[
i
]
for
i
in
model
.
indices
]),
sense
=
maximize
)
self
.
_solver
.
solve
(
model
)
self
.
debug_solve_result
(
model
)
max_rtn
=
self
.
calc_port_rtn
(
model
)
max_var
=
self
.
calc_port_var
(
model
)
minCVaR_whenMaxR
=
self
.
calc_port_cvar
(
model
)
logger
.
debug
({
'max_rtn'
:
max_rtn
,
'max_var'
:
max_var
,
'minCVaR_whenMaxR'
:
minCVaR_whenMaxR
,
})
return
max_rtn
,
max_var
,
minCVaR_whenMaxR
def
solve_min_rtn
(
self
):
...
...
@@ -78,13 +96,21 @@ class MptSolver:
expr
=
sum
([
model
.
w
[
i
]
*
model
.
w
[
j
]
*
self
.
sigma
.
iloc
[
i
,
j
]
for
i
in
model
.
indices
for
j
in
model
.
indices
]),
sense
=
minimize
)
self
.
_solver
.
solve
(
model
)
self
.
debug_solve_result
(
model
)
min_rtn
=
self
.
calc_port_rtn
(
model
)
min_var
=
self
.
calc_port_var
(
model
)
maxCVaR_whenMinV
=
self
.
calc_port_cvar
(
model
)
logger
.
debug
({
'min_rtn'
:
min_rtn
,
'min_var'
:
min_var
,
'maxCVaR_whenMinV'
:
maxCVaR_whenMinV
,
})
return
min_rtn
,
min_var
,
maxCVaR_whenMinV
def
solve_mpt
(
self
,
min_rtn
,
max_rtn
):
big_y
=
min_rtn
+
self
.
get_config
(
'mpt.quantile'
)
*
(
max_rtn
-
min_rtn
)
logger
.
debug
(
f
'...... ...... ...... ...... ...... ...... ...... ...... MPT ... sub risk : pct_value = {self.pct_value}'
)
big_y
=
min_rtn
+
self
.
pct_value
*
(
max_rtn
-
min_rtn
)
logger
.
debug
(
f
'big_Y = target_Return = {big_y}'
)
model
=
self
.
create_model
()
model
.
cons_rtn
=
Constraint
(
expr
=
sum
([
model
.
w
[
i
]
*
self
.
rtn_annualized
[
i
]
for
i
in
model
.
indices
])
>=
big_y
)
model
.
objective
=
Objective
(
...
...
@@ -92,28 +118,37 @@ class MptSolver:
sense
=
minimize
)
result
=
self
.
_solver
.
solve
(
model
)
if
result
.
solver
.
termination_condition
==
TerminationCondition
.
infeasible
:
logger
.
debug
(
'...... MPT: Infeasible Optimization Problem.'
)
return
None
,
None
logger
.
debug
(
'...... MPT: Has solution.'
)
self
.
debug_solve_result
(
model
)
return
self
.
calc_port_weight
(
model
),
self
.
calc_port_cvar
(
model
)
def
solve_poem
(
self
,
min_rtn
,
max_rtn
,
base_cvar
,
max_cvar
):
k_history
=
len
(
self
.
rtn_history
)
quantile
=
self
.
get_config
(
'mpt.quantile'
)
quantile
=
self
.
pct_value
logger
.
debug
(
f
'...... ...... ...... ...... ...... ...... ...... ...... POEM With CVaR constraints ... sub risk : pct_value = {quantile}'
)
big_y
=
min_rtn
+
quantile
*
(
max_rtn
-
min_rtn
)
small_y
=
base_cvar
+
(
max_cvar
-
base_cvar
)
*
self
.
get_config
(
'poem.cvar-scale-factor'
)
*
quantile
logger
.
debug
(
f
'big_Y = target_Return = {big_y} | small_y = target_cvar = {small_y}'
)
model
=
self
.
create_model
()
model
.
alpha
=
Var
(
domain
=
Reals
)
model
.
x
=
Var
(
range
(
k_history
),
domain
=
NonNegativeReals
)
model
.
cons_cvar_aux
=
Constraint
(
range
(
k_history
),
rule
=
lambda
m
,
k
:
m
.
x
[
k
]
>=
m
.
alpha
-
sum
([
m
.
w
[
i
]
*
self
.
rtn_history
[
k
][
i
]
for
i
in
m
.
indices
]))
model
.
cons_cvar_aux
=
Constraint
(
range
(
k_history
),
rule
=
lambda
m
,
k
:
m
.
x
[
k
]
>=
m
.
alpha
-
sum
(
[
m
.
w
[
i
]
*
self
.
rtn_history
[
k
][
i
]
for
i
in
m
.
indices
]))
model
.
cons_rtn
=
Constraint
(
expr
=
sum
([
model
.
w
[
i
]
*
self
.
rtn_annualized
[
i
]
for
i
in
model
.
indices
])
>=
big_y
)
model
.
cons_cvar
=
Constraint
(
expr
=
model
.
alpha
-
(
1
/
self
.
k_beta
)
*
sum
([
model
.
x
[
k
]
for
k
in
range
(
k_history
)])
>=
small_y
)
model
.
cons_cvar
=
Constraint
(
expr
=
model
.
alpha
-
(
1
/
self
.
k_beta
)
*
sum
([
model
.
x
[
k
]
for
k
in
range
(
k_history
)])
>=
small_y
)
result
=
self
.
_solver
.
solve
(
model
)
if
result
.
solver
.
termination_condition
==
TerminationCondition
.
infeasible
:
logger
.
debug
(
'...... POEM: Infeasible Optimization Problem.'
)
return
None
,
None
logger
.
debug
(
'...... POEM: Has solution.'
)
self
.
debug_solve_result
(
model
)
return
self
.
calc_port_weight
(
model
),
self
.
calc_port_cvar
(
model
)
def
calc_port_weight
(
self
,
model
):
id_list
=
self
.
_
navs
.
columns
id_list
=
self
.
navs
.
columns
weight_list
=
[]
for
i
in
model
.
indices
:
weight_list
.
append
(
model
.
w
[
i
]
.
_value
*
model
.
z
[
i
]
.
_value
)
...
...
@@ -140,12 +175,14 @@ class MptSolver:
return
sum
([
model
.
w
[
i
]
.
_value
*
self
.
rtn_annualized
[
i
]
for
i
in
model
.
indices
])
def
calc_port_var
(
self
,
model
):
return
sum
([
model
.
w
[
i
]
.
_value
*
model
.
w
[
j
]
.
_value
*
self
.
sigma
.
iloc
[
i
,
j
]
for
i
in
model
.
indices
for
j
in
model
.
indices
])
return
sum
([
model
.
w
[
i
]
.
_value
*
model
.
w
[
j
]
.
_value
*
self
.
sigma
.
iloc
[
i
,
j
]
for
i
in
model
.
indices
for
j
in
model
.
indices
])
def
calc_port_cvar
(
self
,
model
):
port_r_hist
=
[]
for
k
in
range
(
len
(
self
.
rtn_history
)):
port_r_hist
.
append
(
sum
([
model
.
w
[
i
]
.
_value
*
model
.
z
[
i
]
.
_value
*
self
.
rtn_history
[
k
][
i
]
for
i
in
model
.
indices
]))
port_r_hist
.
append
(
sum
([
model
.
w
[
i
]
.
_value
*
model
.
z
[
i
]
.
_value
*
self
.
rtn_history
[
k
][
i
]
for
i
in
model
.
indices
]))
port_r_hist
.
sort
()
return
sum
(
port_r_hist
[
0
:
self
.
k_beta
])
/
self
.
k_beta
...
...
@@ -161,11 +198,12 @@ class MptSolver:
model
=
ConcreteModel
()
model
.
indices
=
range
(
0
,
len
(
self
.
_
navs
.
columns
))
model
.
indices
=
range
(
0
,
len
(
self
.
navs
.
columns
))
model
.
w
=
Var
(
model
.
indices
,
domain
=
NonNegativeReals
)
model
.
z
=
Var
(
model
.
indices
,
domain
=
Binary
)
model
.
cons_sum_weight
=
Constraint
(
expr
=
sum
([
model
.
w
[
i
]
for
i
in
model
.
indices
])
==
1
)
model
.
cons_num_asset
=
Constraint
(
expr
=
inequality
(
min_count
,
sum
([
model
.
z
[
i
]
for
i
in
model
.
indices
]),
max_count
,
strict
=
False
))
model
.
cons_num_asset
=
Constraint
(
expr
=
inequality
(
min_count
,
sum
([
model
.
z
[
i
]
for
i
in
model
.
indices
]),
max_count
,
strict
=
False
))
model
.
cons_bounds_low
=
Constraint
(
model
.
indices
,
rule
=
lambda
m
,
i
:
m
.
z
[
i
]
*
low_weight
<=
m
.
w
[
i
])
model
.
cons_bounds_up
=
Constraint
(
model
.
indices
,
rule
=
lambda
m
,
i
:
m
.
z
[
i
]
*
high_weight
>=
m
.
w
[
i
])
return
model
...
...
@@ -184,9 +222,11 @@ class MptSolver:
navs
=
navs
.
sort_index
()
navs_nan
=
navs
.
isna
()
.
sum
()
navs
.
drop
(
columns
=
[
x
for
x
in
navs_nan
.
index
if
navs_nan
.
loc
[
x
]
>=
self
.
get_config
(
'navs.max-nan.asset'
)],
inplace
=
True
)
navs
.
drop
(
columns
=
[
x
for
x
in
navs_nan
.
index
if
navs_nan
.
loc
[
x
]
>=
self
.
get_config
(
'navs.max-nan.asset'
)],
inplace
=
True
)
navs_nan
=
navs
.
apply
(
lambda
r
:
r
.
isna
()
.
sum
()
/
len
(
r
),
axis
=
1
)
navs
.
drop
(
index
=
[
x
for
x
in
navs_nan
.
index
if
navs_nan
.
loc
[
x
]
>=
self
.
get_config
(
'navs.max-nan.day'
)],
inplace
=
True
)
navs
.
drop
(
index
=
[
x
for
x
in
navs_nan
.
index
if
navs_nan
.
loc
[
x
]
>=
self
.
get_config
(
'navs.max-nan.day'
)],
inplace
=
True
)
navs
.
fillna
(
method
=
'ffill'
,
inplace
=
True
)
self
.
__navs
=
navs
...
...
@@ -198,11 +238,31 @@ class MptSolver:
else
:
return
None
return
config
value
=
load_config
(
self
.
_config
[
self
.
type
.
value
]
if
self
.
type
is
not
PortfoliosType
.
NORMAL
else
self
.
_config
)
if
value
is
None
:
value
=
load_config
(
self
.
_config
)
return
value
[
f
'ft{self.risk.value}'
]
if
value
and
isinstance
(
value
,
dict
)
else
value
def
debug_solve_result
(
self
,
model
):
if
logger
.
isEnabledFor
(
DEBUG
):
logger
.
debug
(
'==============================='
)
logger
.
debug
(
'solution: id | w(id)'
)
w_sum
=
0
for
i
in
model
.
indices
:
if
model
.
z
[
i
]
.
_value
==
1
:
logger
.
debug
(
f
'{self.navs.columns[i]} | {model.w[i]._value}'
)
w_sum
+=
model
.
w
[
i
]
.
_value
logger
.
debug
(
f
'w_sum = {w_sum}'
)
logger
.
debug
({
'beta'
:
self
.
beta
,
'kbeta'
:
self
.
k_beta
,
'port_R'
:
self
.
calc_port_rtn
(
model
),
'port_V'
:
self
.
calc_port_cvar
(
model
),
'port_CVaR'
:
self
.
calc_port_cvar
(
model
)
})
logger
.
debug
(
'-------------------------------'
)
@
component
(
bean_name
=
'mpt'
)
class
MptPortfoliosBuilder
(
PortfoliosBuilder
):
...
...
@@ -216,67 +276,71 @@ class MptPortfoliosBuilder(PortfoliosBuilder):
def
get_portfolios
(
self
,
day
,
risk
:
PortfoliosRisk
,
type
:
PortfoliosType
=
PortfoliosType
.
NORMAL
):
portfolio
=
rmp
.
get_one
(
day
,
type
,
risk
)
if
not
portfolio
:
self
.
build_portfolio
(
day
,
type
)
result
,
detail
=
self
.
build_portfolio
(
day
,
type
)
for
build_risk
,
datas
in
result
.
items
():
rmp
.
insert
({
**
{
datas
},
'risk'
:
build_risk
,
'type'
:
type
,
'date'
:
day
})
portfolio
=
rmp
.
get_one
(
day
,
type
,
risk
)
return
json
.
loads
(
portfolio
[
'portfolio'
])
if
SolveType
(
portfolio
[
'
rmp_r
olve'
])
is
not
SolveType
.
INFEASIBLE
else
None
return
json
.
loads
(
portfolio
[
'portfolio'
])
if
SolveType
(
portfolio
[
'
s
olve'
])
is
not
SolveType
.
INFEASIBLE
else
None
def
build_portfolio
(
self
,
day
,
type
:
PortfoliosType
):
result
=
{}
detail
=
{}
for
risk
in
PortfoliosRisk
:
logger
.
info
(
f
"start build protfolio of type[{type.name}] and risk[{risk.name}] with date[{format_date(day)}]"
)
solver
=
MptSolver
(
risk
,
type
)
solver
.
reset_navs
(
day
)
logger
.
debug
({
'Khist'
:
len
(
solver
.
rtn_history
),
'beta'
:
solver
.
get_config
(
'mpt.cvar-beta'
),
'Kbeta'
:
solver
.
k_beta
,
})
max_rtn
,
max_var
,
minCVaR_whenMaxR
=
solver
.
solve_max_rtn
()
min_rtn
,
min_var
,
maxCVaR_whenMinV
=
solver
.
solve_min_rtn
()
portfolio
,
cvar
=
solver
.
solve_mpt
(
min_rtn
,
max_rtn
)
if
portfolio
:
rmp
.
insert
({
'date'
:
day
,
'risk'
:
risk
,
'type'
:
type
,
'solve'
:
SolveType
.
MPT
,
'portfolio'
:
json
.
dumps
(
portfolio
),
'cvar'
:
cvar
})
else
:
rmp
.
insert
({
'date'
:
day
,
'risk'
:
risk
,
'type'
:
type
,
'solve'
:
SolveType
.
INFEASIBLE
})
result
[
risk
]
=
{
'solve'
:
SolveType
.
MPT
,
'portfolio'
:
json
.
dumps
(
portfolio
)
,
'cvar'
:
cvar
}
if
portfolio
else
{
'solve'
:
SolveType
.
INFEASIBLE
}
detail
[
risk
]
=
{
'max_rtn'
:
max_rtn
,
'max_var'
:
max_var
,
'minCVaR_whenMaxR'
:
minCVaR_whenMaxR
,
'min_rtn'
:
min_rtn
,
'min_var'
:
min_var
,
'maxCVaR_whenMinV'
:
maxCVaR_whenMinV
,
}
return
result
,
detail
@
component
(
bean_name
=
'poem'
)
class
PoemPortfoliosBuilder
(
MptPortfoliosBuilder
):
def
build_portfolio
(
self
,
day
,
type
:
PortfoliosType
):
result
,
detail
=
super
(
PoemPortfoliosBuilder
,
self
)
.
build_portfolio
(
day
,
type
)
for
risk
in
PortfoliosRisk
:
if
result
[
risk
][
'solve'
]
is
SolveType
.
INFEASIBLE
:
continue
solver
=
MptSolver
(
risk
,
type
)
solver
.
reset_navs
(
day
)
max_rtn
,
max_var
,
minCVaR_whenMaxR
=
solver
.
solve_max_rtn
()
min_rtn
,
min_var
,
maxCVaR_whenMinV
=
solver
.
solve_min_rtn
()
portfolio
,
cvar
=
solver
.
solve_mpt
(
min_rtn
,
max_rtn
)
solve
=
SolveType
.
MPT
if
portfolio
is
not
None
:
poem_port
,
poem_cvar
=
solver
.
solve_poem
(
min_rtn
,
max_rtn
,
cvar
,
maxCVaR_whenMinV
)
if
poem_port
:
portfolio
=
poem_port
cvar
=
poem_cvar
solve
=
SolveType
.
POEM
min_rtn
=
detail
[
risk
][
'min_rtn'
]
max_rtn
=
detail
[
risk
][
'max_rtn'
]
mpt_cvar
=
result
[
risk
][
'cvar'
]
maxCVaR_whenMinV
=
detail
[
risk
][
'maxCVaR_whenMinV'
]
portfolio
,
cvar
=
solver
.
solve_poem
(
min_rtn
,
max_rtn
,
mpt_cvar
,
maxCVaR_whenMinV
)
if
portfolio
:
rmp
.
insert
({
'date'
:
day
,
'risk'
:
risk
,
'type'
:
type
,
'solve'
:
solve
,
result
[
risk
]
=
{
'solve'
:
SolveType
.
POEM
,
'portfolio'
:
json
.
dumps
(
portfolio
),
'cvar'
:
cvar
})
else
:
rmp
.
insert
({
'date'
:
day
,
'risk'
:
risk
,
'type'
:
type
,
'solve'
:
SolveType
.
INFEASIBLE
})
}
detail
[
risk
][
'mpt_cvar'
]
=
mpt_cvar
return
result
,
detail
portfolios/dao/robo_mpt_portfolios.py
View file @
495d15b0
from
datetime
import
datetime
from
framework
import
read
,
write
,
where
,
format_date
from
enum
import
Enum
from
api
import
PortfoliosRisk
,
PortfoliosType
from
framework
import
read
,
write
,
where
,
format_date
__COLUMNS__
=
{
'rmp_id'
:
'id'
,
...
...
@@ -31,6 +32,6 @@ def insert(datas):
@
read
(
one
=
True
)
def
get_one
(
day
,
type
:
PortfoliosType
,
risk
:
PortfoliosRisk
):
return
f
'''
select {','.join([
x for x in __COLUMNS__.key
s()])} from robo_mpt_portfolios
select {','.join([
f"{x[0]} as {x[1]}" for x in __COLUMNS__.item
s()])} from robo_mpt_portfolios
{where(rmp_date=day, rmp_risk=risk, rmp_type=type)}
'''
test.py
0 → 100644
View file @
495d15b0
from
api
import
PortfoliosBuilder
,
PortfoliosType
from
framework
import
autowired
,
parse_date
,
get_logger
logger
=
get_logger
(
'test'
)
@
autowired
(
names
=
{
'builder'
:
'poem'
})
def
test_portfolio_builder
(
builder
:
PortfoliosBuilder
=
None
):
result
,
detail
=
builder
.
build_portfolio
(
parse_date
(
'2011-11-07'
),
PortfoliosType
.
NORMAL
)
logger
.
info
(
result
)
logger
.
info
(
detail
)
if
__name__
==
'__main__'
:
test_portfolio_builder
()
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