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
65c7bb8d
Commit
65c7bb8d
authored
Mar 06, 2023
by
jichao
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
重构导出模块
添加邮件发送
parent
cb6e2cc7
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
280 additions
and
87 deletions
+280
-87
api.py
api.py
+10
-2
datum.py
basic/datum.py
+1
-1
config-svrobo3.yml
config-svrobo3.yml
+36
-15
builder.py
portfolios/builder.py
+1
-1
holder.py
portfolios/holder.py
+39
-1
test_case.py
portfolios/test_case.py
+5
-0
ruler.py
rebalance/ruler.py
+50
-3
test_case.py
rebalance/test_case.py
+5
-0
backtest.py
reports/backtest.py
+0
-57
benchmark.py
reports/benchmark.py
+1
-1
exports.py
reports/exports.py
+120
-0
fixed_range.py
reports/fixed_range.py
+3
-2
test_case.py
reports/test_case.py
+9
-4
No files found.
api.py
View file @
65c7bb8d
...
@@ -557,6 +557,15 @@ class RebalanceRuler(ABC):
...
@@ -557,6 +557,15 @@ class RebalanceRuler(ABC):
'''
'''
pass
pass
@
abstractmethod
def
get_signal_date
(
self
,
sign_id
):
'''
获取指定id的信号日期
:param sign_id: 信号id, 可以多个,使用元祖包裹
:return: 信号日期
'''
pass
@
abstractmethod
@
abstractmethod
def
clear_signal
(
self
,
day
=
None
,
risk
:
PortfoliosRisk
=
None
):
def
clear_signal
(
self
,
day
=
None
,
risk
:
PortfoliosRisk
=
None
):
'''
'''
...
@@ -625,9 +634,8 @@ class RoboExportor(ABC):
...
@@ -625,9 +634,8 @@ class RoboExportor(ABC):
@
abstractmethod
@
abstractmethod
def
export
(
self
,
max_date
=
dt
.
today
(),
min_date
=
None
):
def
export
(
self
,
max_date
=
dt
.
today
(),
min_date
=
None
):
'''
'''
导出指定日期的数据到excel
根据参数以及配置信息执行导出相关操作
:param max_date: 指定截止日期
:param max_date: 指定截止日期
:param min_date: 指定开始日期
:param min_date: 指定开始日期
:return: 导出文件路径
'''
'''
pass
pass
basic/datum.py
View file @
65c7bb8d
...
@@ -55,7 +55,7 @@ class DefaultDatum(Datum):
...
@@ -55,7 +55,7 @@ class DefaultDatum(Datum):
datum_ids
=
tuple
(
set
(
datum_ids
or
[])
|
{
x
[
'id'
]
for
x
in
datums
})
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
=
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
]
result
=
[{
**
json
.
loads
(
x
[
'datas'
]),
'id'
:
x
[
'id'
]}
for
x
in
result
]
return
[
self
.
format_datum
(
x
)
for
x
in
result
if
not
exclude
or
x
[
'bloombergTicker'
]
not
in
self
.
excludes
]
return
[
self
.
format_datum
(
x
)
for
x
in
result
if
not
exclude
or
x
[
'
id'
]
in
(
datum_ids
or
[])
or
x
[
'
bloombergTicker'
]
not
in
self
.
excludes
]
def
get_high_risk_datums
(
self
,
risk
:
PortfoliosRisk
):
def
get_high_risk_datums
(
self
,
risk
:
PortfoliosRisk
):
risk3
=
self
.
get_datums
(
type
=
DatumType
.
FUND
,
risk
=
3
)
risk3
=
self
.
get_datums
(
type
=
DatumType
.
FUND
,
risk
=
3
)
...
...
config-svrobo3.yml
View file @
65c7bb8d
...
@@ -227,21 +227,42 @@ reports: # 报告模块相关
...
@@ -227,21 +227,42 @@ reports: # 报告模块相关
name
:
'
五年'
name
:
'
五年'
-
years
:
10
-
years
:
10
name
:
'
十年'
name
:
'
十年'
backtest
:
# 回测导出曹策略
exports
:
exist-build
:
on
# 如果报告文件存在,是否重新构建文件
backtest
:
# 回测导出曹策略
save-path
:
${EXPORT_PATH:excels}
# 导出报告文件存放路径,如果以./或者../开头,则会以执行python文件为根目录,如果以/开头,则为系统绝对路径,否则,以项目目录为根目录
save-path
:
${EXPORT_PATH:excels}
# 导出报告文件存放路径,如果以./或者../开头,则会以执行python文件为根目录,如果以/开头,则为系统绝对路径,否则,以项目目录为根目录
file-name
:
${EXPORT_FILENAME:real}
file-name
:
${EXPORT_FILENAME:real}
include-report
:
# 需要导出的报告类型列表,下面的顺序,也代表了excel中sheet的顺序
include-report
:
# 需要导出的报告类型列表,下面的顺序,也代表了excel中sheet的顺序
# - funds-report # 基金资料
# - funds-report # 基金资料
# - navs-report # 净值报告
# - navs-report # 净值报告
-
hold-report
# 持仓报告
-
hold-report
# 持仓报告
-
signal-report
# 信号报告
-
signal-report
# 信号报告
-
asset-pool-report
# 基金池报告
-
asset-pool-report
# 基金池报告
-
mpt-report
# 最优投组报告
-
mpt-report
# 最优投组报告
-
benckmark-report
# benckmark报告
-
benckmark-report
# benckmark报告
-
indicators-report
# 各种特殊指标报告
-
indicators-report
# 各种特殊指标报告
-
fixed-range-report
# 固定区间收益报告
-
fixed-range-report
# 固定区间收益报告
-
relative-range-report
# 相对区间收益报告
-
relative-range-report
# 相对区间收益报告
real-daily
:
file-name
:
${EXPORT_FILENAME:svrobo3_portfolios}
include-report
:
-
daily-hold-report
-
daily-signal-report
email
:
receives
:
-
jichao@thizgroup.com
# copies:
# - Tony.Wu.Home@gmail.com
# - jinghan.yang@chifuinvestments.com
# - will.xu@thizgroup.com
# - brody_wu@chifufund.com
# - telan_qian@chifufund.com
# - tina.yang@thizgroup.com
subject
:
default
:
"
ROBO_TAIBEI-实盘版-每日投組推薦_{today}"
rebalance
:
"
ROBO_TAIBEI-实盘版-每日投組推薦_{today}_今日有調倉信號!!!"
content
:
default
:
"
Dear
All:
附件是今天生成的推薦組合,請驗收,謝謝!
注>:該郵件為自動發送,如有問題請聯繫矽谷團隊
telan_qian@chifufund.com"
rebalance
:
"
Dear
All:
附件是今天生成的推薦組合以及調倉信號,請驗收,謝謝!
注>:該郵件為自動發送,如有問題請聯繫矽谷團隊
telan_qian@chifufund.com"
robo-executor
:
# 执行器相关
robo-executor
:
# 执行器相关
use
:
${ROBO_EXECUTOR:real}
# 执行哪个执行器,优先取系统环境变量ROBO_EXECUTOR的值,默认backtest
use
:
${ROBO_EXECUTOR:real}
# 执行哪个执行器,优先取系统环境变量ROBO_EXECUTOR的值,默认backtest
sync-data
:
${SYNC_DATA:on}
# 是否开启同步资料数据
sync-data
:
${SYNC_DATA:on}
# 是否开启同步资料数据
...
...
portfolios/builder.py
View file @
65c7bb8d
...
@@ -123,7 +123,7 @@ class MptReportor(RoboReportor):
...
@@ -123,7 +123,7 @@ class MptReportor(RoboReportor):
def
load_report
(
self
,
max_date
=
dt
.
today
(),
min_date
=
None
)
->
List
[
dict
]:
def
load_report
(
self
,
max_date
=
dt
.
today
(),
min_date
=
None
)
->
List
[
dict
]:
results
=
[]
results
=
[]
datums
=
{
x
[
'id'
]:
x
for
x
in
self
.
_datum
.
get_datums
(
type
=
DatumType
.
FUND
)}
datums
=
{
x
[
'id'
]:
x
for
x
in
self
.
_datum
.
get_datums
(
type
=
DatumType
.
FUND
,
exclude
=
False
)}
for
portfolio
in
rmp
.
get_list
(
max_date
=
max_date
,
min_date
=
min_date
):
for
portfolio
in
rmp
.
get_list
(
max_date
=
max_date
,
min_date
=
min_date
):
solve_type
=
SolveType
(
portfolio
[
'solve'
])
solve_type
=
SolveType
(
portfolio
[
'solve'
])
datas
=
{
datas
=
{
...
...
portfolios/holder.py
View file @
65c7bb8d
...
@@ -2,13 +2,14 @@ import json
...
@@ -2,13 +2,14 @@ import json
import
logging
import
logging
from
datetime
import
datetime
as
dt
from
datetime
import
datetime
as
dt
from
typing
import
List
from
typing
import
List
from
functools
import
reduce
import
pandas
as
pd
import
pandas
as
pd
from
py_jftech
import
(
from
py_jftech
import
(
component
,
autowired
,
get_config
,
next_workday
,
prev_workday
,
transaction
,
workday_range
,
format_date
component
,
autowired
,
get_config
,
next_workday
,
prev_workday
,
transaction
,
workday_range
,
format_date
)
)
from
api
import
PortfoliosHolder
,
PortfoliosRisk
,
RebalanceRuler
,
Navs
,
SignalType
,
RoboExecutor
,
PortfoliosType
,
RoboReportor
from
api
import
PortfoliosHolder
,
PortfoliosRisk
,
RebalanceRuler
,
Navs
,
SignalType
,
RoboExecutor
,
PortfoliosType
,
RoboReportor
,
Datum
,
DatumType
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
...
@@ -155,3 +156,40 @@ class HoldReportor(RoboReportor):
...
@@ -155,3 +156,40 @@ class HoldReportor(RoboReportor):
holds
=
holds
[[
'risk'
,
'date'
,
'nav'
,
'signal_type'
]]
holds
=
holds
[[
'risk'
,
'date'
,
'nav'
,
'signal_type'
]]
return
holds
.
to_dict
(
'records'
)
return
holds
.
to_dict
(
'records'
)
return
[]
return
[]
@
component
(
bean_name
=
'daily-hold-report'
)
class
DailyHoldReportor
(
RoboReportor
):
@
autowired
def
__init__
(
self
,
rule
:
RebalanceRuler
=
None
,
datum
:
Datum
=
None
):
self
.
_rule
=
rule
self
.
_datum
=
datum
@
property
def
report_name
(
self
)
->
str
:
return
'每日持仓信息'
def
load_report
(
self
,
max_date
=
prev_workday
(
dt
.
today
()),
min_date
=
None
)
->
List
[
dict
]:
holds
=
pd
.
DataFrame
(
rhp
.
get_list
(
max_date
=
max_date
,
min_date
=
min_date
))
holds
=
holds
[
holds
[
'date'
]
.
dt
.
date
==
max_date
.
date
()]
if
not
holds
.
empty
:
signal_types
=
self
.
_rule
.
get_signal_type
(
tuple
(
set
(
holds
[
'signal_id'
])))
signal_dates
=
self
.
_rule
.
get_signal_date
(
tuple
(
set
(
holds
[
'signal_id'
])))
datum_ids
=
reduce
(
lambda
x
,
y
:
x
|
y
,
holds
[
'portfolios'
]
.
apply
(
lambda
x
:
set
(
json
.
loads
(
x
)[
'weight'
]
.
keys
())))
datums
=
pd
.
DataFrame
(
self
.
_datum
.
get_datums
(
type
=
DatumType
.
FUND
,
datum_ids
=
datum_ids
))
datums
.
set_index
(
'id'
,
inplace
=
True
)
holds
[
'rebalance_type'
]
=
holds
.
apply
(
lambda
row
:
signal_types
[
row
[
'signal_id'
]]
.
name
,
axis
=
1
)
holds
[
'rebalance_date'
]
=
holds
.
apply
(
lambda
row
:
signal_dates
[
row
[
'signal_id'
]],
axis
=
1
)
holds
[
'risk'
]
=
holds
.
apply
(
lambda
row
:
PortfoliosRisk
(
row
[
'risk'
])
.
name
,
axis
=
1
)
holds
[
'portfolios'
]
=
holds
.
apply
(
lambda
row
:
[
x
for
x
in
json
.
loads
(
row
[
'portfolios'
])[
'weight'
]
.
items
()],
axis
=
1
)
holds
=
holds
.
explode
(
'portfolios'
,
ignore_index
=
True
)
holds
[
'weight'
]
=
holds
.
apply
(
lambda
row
:
row
[
'portfolios'
][
1
],
axis
=
1
)
holds
[
'asset_ids'
]
=
holds
.
apply
(
lambda
row
:
datums
.
loc
[
int
(
row
[
'portfolios'
][
0
])][
'ftTicker'
],
axis
=
1
)
holds
[
'name'
]
=
holds
.
apply
(
lambda
row
:
datums
.
loc
[
int
(
row
[
'portfolios'
][
0
])][
'chineseName'
],
axis
=
1
)
holds
[
'lipper_id'
]
=
holds
.
apply
(
lambda
row
:
datums
.
loc
[
int
(
row
[
'portfolios'
][
0
])][
'lipperKey'
],
axis
=
1
)
holds
=
holds
[[
'risk'
,
'date'
,
'asset_ids'
,
'weight'
,
'rebalance_type'
,
'rebalance_date'
,
'name'
,
'lipper_id'
]]
return
holds
.
to_dict
(
'records'
)
return
[]
portfolios/test_case.py
View file @
65c7bb8d
...
@@ -40,6 +40,11 @@ class PortfoliosTest(unittest.TestCase):
...
@@ -40,6 +40,11 @@ class PortfoliosTest(unittest.TestCase):
def
test_clear
(
self
,
hold
:
PortfoliosHolder
=
None
):
def
test_clear
(
self
,
hold
:
PortfoliosHolder
=
None
):
hold
.
clear
()
hold
.
clear
()
@
autowired
(
names
=
{
'reportor'
:
'daily-hold-report'
})
def
test_daily_hold_report
(
self
,
reportor
:
RoboReportor
=
None
):
report
=
reportor
.
load_report
()
self
.
logger
.
info
(
to_str
(
report
))
if
__name__
==
'__main__'
:
if
__name__
==
'__main__'
:
unittest
.
main
()
unittest
.
main
()
rebalance/ruler.py
View file @
65c7bb8d
import
json
import
json
from
datetime
import
datetime
as
dt
from
datetime
import
datetime
as
dt
from
typing
import
List
,
Dict
from
typing
import
List
,
Dict
from
functools
import
reduce
from
py_jftech
import
component
,
autowired
,
get_config
,
workday_range
,
next_workday
,
to_tuple
import
pandas
as
pd
from
py_jftech
import
component
,
autowired
,
get_config
,
workday_range
,
next_workday
,
to_tuple
,
prev_workday
from
api
import
RebalanceRuler
,
PortfoliosRisk
,
RebalanceSignal
,
SignalType
,
PortfoliosType
,
PortfoliosHolder
,
RoboReportor
,
Datum
,
DatumType
from
api
import
RebalanceRuler
,
PortfoliosRisk
,
RebalanceSignal
,
SignalType
,
PortfoliosType
,
PortfoliosHolder
,
RoboReportor
,
Datum
,
DatumType
from
rebalance.dao
import
robo_rebalance_signal
as
rrs
from
rebalance.dao
import
robo_rebalance_signal
as
rrs
...
@@ -93,6 +95,14 @@ class LevelRebalanceRuler(RebalanceRuler):
...
@@ -93,6 +95,14 @@ class LevelRebalanceRuler(RebalanceRuler):
signal
=
rrs
.
get_by_id
(
sign_id
[
0
])
signal
=
rrs
.
get_by_id
(
sign_id
[
0
])
return
SignalType
(
signal
[
'type'
])
if
signal
else
None
return
SignalType
(
signal
[
'type'
])
if
signal
else
None
def
get_signal_date
(
self
,
sign_id
):
sign_id
=
to_tuple
(
sign_id
)
if
len
(
sign_id
)
>
1
:
return
{
x
[
'id'
]:
x
[
'date'
]
for
x
in
rrs
.
get_by_ids
(
sign_id
)}
else
:
signal
=
rrs
.
get_by_id
(
sign_id
[
0
])
return
signal
[
'date'
]
if
signal
else
None
def
commit_signal
(
self
,
sign_id
):
def
commit_signal
(
self
,
sign_id
):
rrs
.
update
(
sign_id
,
{
'effective'
:
True
})
rrs
.
update
(
sign_id
,
{
'effective'
:
True
})
...
@@ -101,7 +111,7 @@ class LevelRebalanceRuler(RebalanceRuler):
...
@@ -101,7 +111,7 @@ class LevelRebalanceRuler(RebalanceRuler):
@
component
(
bean_name
=
'signal-report'
)
@
component
(
bean_name
=
'signal-report'
)
class
Signal
Ex
portor
(
RoboReportor
):
class
Signal
Re
portor
(
RoboReportor
):
@
autowired
@
autowired
def
__init__
(
self
,
hold
:
PortfoliosHolder
=
None
,
datum
:
Datum
=
None
):
def
__init__
(
self
,
hold
:
PortfoliosHolder
=
None
,
datum
:
Datum
=
None
):
...
@@ -114,7 +124,7 @@ class SignalExportor(RoboReportor):
...
@@ -114,7 +124,7 @@ class SignalExportor(RoboReportor):
def
load_report
(
self
,
max_date
=
dt
.
today
(),
min_date
=
None
)
->
List
[
dict
]:
def
load_report
(
self
,
max_date
=
dt
.
today
(),
min_date
=
None
)
->
List
[
dict
]:
result
=
[]
result
=
[]
datums
=
{
str
(
x
[
'id'
]):
x
for
x
in
self
.
_datum
.
get_datums
(
type
=
DatumType
.
FUND
)}
datums
=
{
str
(
x
[
'id'
]):
x
for
x
in
self
.
_datum
.
get_datums
(
type
=
DatumType
.
FUND
,
exclude
=
False
)}
for
signal
in
rrs
.
get_list
(
max_date
=
max_date
,
min_date
=
min_date
,
effective
=
True
):
for
signal
in
rrs
.
get_list
(
max_date
=
max_date
,
min_date
=
min_date
,
effective
=
True
):
rebalance_date
=
self
.
_hold
.
get_rebalance_date_by_signal
(
signal
[
'id'
])
rebalance_date
=
self
.
_hold
.
get_rebalance_date_by_signal
(
signal
[
'id'
])
for
fund_id
,
weight
in
json
.
loads
(
signal
[
'portfolio'
])
.
items
():
for
fund_id
,
weight
in
json
.
loads
(
signal
[
'portfolio'
])
.
items
():
...
@@ -130,3 +140,40 @@ class SignalExportor(RoboReportor):
...
@@ -130,3 +140,40 @@ class SignalExportor(RoboReportor):
'weight'
:
weight
'weight'
:
weight
})
})
return
result
return
result
@
component
(
bean_name
=
'daily-signal-report'
)
class
DailySignalReportor
(
RoboReportor
):
@
autowired
def
__init__
(
self
,
hold
:
PortfoliosHolder
=
None
,
datum
:
Datum
=
None
):
self
.
_hold
=
hold
self
.
_datum
=
datum
@
property
def
report_name
(
self
)
->
str
:
return
'每日调仓信号'
def
load_report
(
self
,
max_date
=
prev_workday
(
dt
.
today
()),
min_date
=
None
)
->
List
[
dict
]:
signals
=
pd
.
DataFrame
(
rrs
.
get_list
(
max_date
=
max_date
,
min_date
=
min_date
))
signals
=
signals
[(
signals
[
'date'
]
.
dt
.
date
==
max_date
.
date
())
&
(
signals
[
'type'
]
!=
SignalType
.
NONE
.
value
)]
if
not
signals
.
empty
:
datum_ids
=
reduce
(
lambda
x
,
y
:
x
|
y
,
signals
[
'portfolio'
]
.
apply
(
lambda
x
:
set
(
json
.
loads
(
x
)
.
keys
())))
datums
=
pd
.
DataFrame
(
self
.
_datum
.
get_datums
(
type
=
DatumType
.
FUND
,
datum_ids
=
datum_ids
))
datums
.
set_index
(
'id'
,
inplace
=
True
)
signals
[
'risk'
]
=
signals
.
apply
(
lambda
row
:
PortfoliosRisk
(
row
[
'risk'
])
.
name
,
axis
=
1
)
signals
[
'rebalance_type'
]
=
signals
.
apply
(
lambda
row
:
SignalType
(
row
[
'type'
])
.
name
,
axis
=
1
)
signals
[
'portfolio_type'
]
=
signals
.
apply
(
lambda
row
:
PortfoliosType
(
row
[
'portfolio_type'
])
.
name
,
axis
=
1
)
signals
[
'portfolio'
]
=
signals
.
apply
(
lambda
row
:
[
x
for
x
in
json
.
loads
(
row
[
'portfolio'
])
.
items
()],
axis
=
1
)
signals
=
signals
.
explode
(
'portfolio'
,
ignore_index
=
True
)
signals
[
'weight'
]
=
signals
.
apply
(
lambda
row
:
row
[
'portfolio'
][
1
],
axis
=
1
)
signals
[
'asset_ids'
]
=
signals
.
apply
(
lambda
row
:
datums
.
loc
[
int
(
row
[
'portfolio'
][
0
])][
'ftTicker'
],
axis
=
1
)
signals
[
'name'
]
=
signals
.
apply
(
lambda
row
:
datums
.
loc
[
int
(
row
[
'portfolio'
][
0
])][
'chineseName'
],
axis
=
1
)
signals
[
'lipper_id'
]
=
signals
.
apply
(
lambda
row
:
datums
.
loc
[
int
(
row
[
'portfolio'
][
0
])][
'lipperKey'
],
axis
=
1
)
signals
=
signals
[[
'risk'
,
'date'
,
'rebalance_type'
,
'asset_ids'
,
'lipper_id'
,
'name'
,
'weight'
]]
return
signals
.
to_dict
(
'records'
)
return
[]
rebalance/test_case.py
View file @
65c7bb8d
...
@@ -54,6 +54,11 @@ class RebalanceTest(unittest.TestCase):
...
@@ -54,6 +54,11 @@ class RebalanceTest(unittest.TestCase):
result
=
reportor
.
load_report
()
result
=
reportor
.
load_report
()
logger
.
info
(
to_str
(
result
,
show_line
=
10
))
logger
.
info
(
to_str
(
result
,
show_line
=
10
))
@
autowired
(
names
=
{
'reportor'
:
'daily-signal-report'
})
def
test_daily_signal_report
(
self
,
reportor
:
RoboReportor
=
None
):
result
=
reportor
.
load_report
(
max_date
=
parse_date
(
'2022-11-21'
))
logger
.
info
(
to_str
(
result
,
show_line
=
10
))
@
autowired
@
autowired
def
test_clear_signal
(
self
,
ruler
:
RebalanceRuler
=
None
):
def
test_clear_signal
(
self
,
ruler
:
RebalanceRuler
=
None
):
ruler
.
clear_signal
()
ruler
.
clear_signal
()
...
...
reports/backtest.py
deleted
100644 → 0
View file @
cb6e2cc7
import
os
from
datetime
import
datetime
as
dt
from
typing
import
List
import
pandas
as
pd
from
py_jftech
import
component
,
autowired
,
get_config
,
get_instance_name
,
get_project_path
,
format_date
from
api
import
RoboReportor
,
RoboExportor
def
include_report
():
return
get_config
(
__name__
)[
'include-report'
]
@
component
(
bean_name
=
'backtest-export'
)
class
BacktestExportor
(
RoboExportor
):
@
autowired
(
includes
=
{
'reportors'
:
include_report
()})
def
__init__
(
self
,
reportors
:
List
[
RoboReportor
]
=
None
):
reportors
=
{
get_instance_name
(
x
):
x
for
x
in
reportors
}
self
.
_reportors
:
List
[
RoboReportor
]
=
[
reportors
[
x
]
for
x
in
include_report
()]
self
.
_config
=
get_config
(
__name__
)
@
property
def
save_path
(
self
):
save_path
:
str
=
self
.
_config
[
'save-path'
]
if
save_path
.
startswith
(
'.'
):
return
os
.
path
.
abspath
(
os
.
path
.
join
(
os
.
path
.
dirname
(
__file__
),
save_path
))
elif
save_path
.
startswith
(
'/'
):
return
os
.
path
.
abspath
(
save_path
)
return
os
.
path
.
abspath
(
os
.
path
.
join
(
get_project_path
(),
save_path
))
@
property
def
exist_build
(
self
):
return
self
.
_config
[
'exist-build'
]
@
property
def
file_name
(
self
):
return
self
.
_config
[
'file-name'
]
if
'file-name'
in
self
.
_config
else
'backtest'
def
export
(
self
,
max_date
=
dt
.
today
(),
min_date
=
None
):
root
=
self
.
save_path
os
.
makedirs
(
root
,
exist_ok
=
True
)
filename
=
f
"{self.file_name}_{format_date(max_date)}.xlsx"
if
min_date
:
filename
=
f
"{self.file_name}_{format_date(min_date)}_to_{format_date(max_date)}.xlsx"
file
=
os
.
path
.
join
(
root
,
filename
)
if
os
.
path
.
exists
(
file
):
if
not
self
.
exist_build
:
return
file
os
.
remove
(
file
)
with
pd
.
ExcelWriter
(
file
)
as
writer
:
for
reportor
in
self
.
_reportors
:
datas
=
pd
.
DataFrame
(
reportor
.
load_report
(
max_date
=
max_date
,
min_date
=
min_date
))
if
not
datas
.
empty
:
datas
.
to_excel
(
writer
,
sheet_name
=
reportor
.
report_name
,
index
=
False
)
return
file
reports/benchmark.py
View file @
65c7bb8d
...
@@ -35,7 +35,7 @@ class BenchmarkReportor(RoboReportor):
...
@@ -35,7 +35,7 @@ class BenchmarkReportor(RoboReportor):
def
load_nav_rtn
(
self
,
risk
,
day
):
def
load_nav_rtn
(
self
,
risk
,
day
):
last
=
rb
.
get_last_one
(
risk
=
risk
,
max_date
=
day
,
re
=
True
)
last
=
rb
.
get_last_one
(
risk
=
risk
,
max_date
=
day
,
re
=
True
)
start_date
=
last
[
'date'
]
if
last
else
next_workday
(
self
.
_exec
.
start_date
)
start_date
=
next_workday
(
last
[
'date'
])
if
last
else
self
.
_exec
.
start_date
datums
=
{
x
[
'id'
]:
x
for
x
in
self
.
_datum
.
get_datums
(
type
=
DatumType
.
FUND
)}
datums
=
{
x
[
'id'
]:
x
for
x
in
self
.
_datum
.
get_datums
(
type
=
DatumType
.
FUND
)}
navs
=
pd
.
DataFrame
(
self
.
_navs
.
get_fund_navs
(
fund_ids
=
tuple
(
datums
.
keys
()),
min_date
=
prev_workday
(
start_date
-
timedelta
(
10
)),
max_date
=
day
))
navs
=
pd
.
DataFrame
(
self
.
_navs
.
get_fund_navs
(
fund_ids
=
tuple
(
datums
.
keys
()),
min_date
=
prev_workday
(
start_date
-
timedelta
(
10
)),
max_date
=
day
))
...
...
reports/exports.py
0 → 100644
View file @
65c7bb8d
import
os
from
datetime
import
datetime
as
dt
from
typing
import
List
from
abc
import
abstractmethod
,
ABCMeta
from
tempfile
import
TemporaryDirectory
from
shutil
import
copyfile
from
copy
import
deepcopy
import
pandas
as
pd
from
py_jftech
import
component
,
autowired
,
get_config
,
get_instance_name
,
get_project_path
,
format_date
,
sendmail
from
api
import
RoboReportor
,
RoboExportor
def
include_report
():
return
get_config
(
__name__
)[
'include-report'
]
class
DefaultExportor
(
RoboExportor
):
@
autowired
def
__init__
(
self
,
reportors
:
List
[
RoboReportor
]
=
None
):
self
.
_reportors
=
{
get_instance_name
(
x
):
x
for
x
in
reportors
}
def
export
(
self
,
max_date
=
dt
.
today
(),
min_date
=
None
):
if
not
self
.
include_report
:
return
None
with
TemporaryDirectory
()
as
tmpdir
:
filename
=
f
"{self.file_name}_{format_date(max_date)}.xlsx"
if
min_date
:
filename
=
f
"{self.file_name}_{format_date(min_date)}_to_{format_date(max_date)}.xlsx"
filepath
=
os
.
path
.
join
(
tmpdir
,
filename
)
with
pd
.
ExcelWriter
(
filepath
)
as
writer
:
for
reportor_name
in
self
.
include_report
:
reportor
=
self
.
_reportors
[
reportor_name
]
datas
=
pd
.
DataFrame
(
reportor
.
load_report
(
max_date
=
max_date
,
min_date
=
min_date
))
if
not
datas
.
empty
:
datas
.
to_excel
(
writer
,
sheet_name
=
reportor
.
report_name
,
index
=
False
)
email
=
self
.
get_email
(
filepath
)
if
email
:
receives
=
email
[
'receives'
]
copies
=
email
[
'copies'
]
if
'copies'
in
email
else
[]
attach_paths
=
[
filepath
]
subject
=
email
[
'subject'
]
.
format
(
today
=
format_date
(
dt
.
today
()),
max_date
=
max_date
,
min_date
=
min_date
)
content
=
email
[
'content'
]
.
format
(
today
=
format_date
(
dt
.
today
()),
max_date
=
max_date
,
min_date
=
min_date
)
sendmail
(
receives
=
receives
,
copies
=
copies
,
attach_paths
=
attach_paths
,
subject
=
subject
,
content
=
content
)
if
self
.
save_path
is
not
None
:
os
.
makedirs
(
self
.
save_path
,
exist_ok
=
True
)
save_file
=
os
.
path
.
join
(
self
.
save_path
,
filename
)
copyfile
(
filepath
,
save_file
)
def
get_email
(
self
,
file
):
return
deepcopy
(
self
.
config
[
'email'
])
if
'email'
in
self
.
config
else
None
@
property
def
save_path
(
self
):
if
'save-path'
not
in
self
.
config
:
return
None
save_path
:
str
=
self
.
config
[
'save-path'
]
if
save_path
.
startswith
(
'.'
):
return
os
.
path
.
abspath
(
os
.
path
.
join
(
os
.
path
.
dirname
(
__file__
),
save_path
))
elif
save_path
.
startswith
(
'/'
):
return
os
.
path
.
abspath
(
save_path
)
return
os
.
path
.
abspath
(
os
.
path
.
join
(
get_project_path
(),
save_path
))
@
property
def
exist_build
(
self
):
return
self
.
config
[
'exist-build'
]
if
'exist-build'
in
self
.
config
else
False
@
property
def
file_name
(
self
):
return
self
.
config
[
'file-name'
]
if
'file-name'
in
self
.
config
else
'export'
@
property
def
include_report
(
self
):
return
self
.
config
[
'include-report'
]
if
'include-report'
in
self
.
config
else
[]
@
property
@
abstractmethod
def
config
(
self
):
pass
@
component
(
bean_name
=
'backtest-export'
)
class
BacktestExportor
(
DefaultExportor
):
def
__init__
(
self
):
super
(
BacktestExportor
,
self
)
.
__init__
()
self
.
__config
=
deepcopy
(
get_config
(
__name__
))
@
property
def
config
(
self
):
return
self
.
__config
[
'backtest'
]
@
component
(
bean_name
=
'real-daily-export'
)
class
RealDailyExportor
(
DefaultExportor
):
@
autowired
(
names
=
{
'signal_reportor'
:
'daily-signal-report'
})
def
__init__
(
self
,
signal_reportor
:
RoboReportor
=
None
):
super
(
RealDailyExportor
,
self
)
.
__init__
()
self
.
__config
=
get_config
(
__name__
)
self
.
_signal_reportor
=
signal_reportor
def
get_email
(
self
,
file
):
result
=
super
(
RealDailyExportor
,
self
)
.
get_email
(
file
)
if
result
is
None
:
return
None
content
=
pd
.
read_excel
(
file
,
sheet_name
=
None
)
if
self
.
_signal_reportor
.
report_name
in
content
:
result
[
'subject'
]
=
str
(
result
[
'subject'
][
'rebalance'
])
result
[
'content'
]
=
result
[
'content'
][
'rebalance'
]
else
:
result
[
'subject'
]
=
result
[
'subject'
][
'default'
]
result
[
'content'
]
=
result
[
'content'
][
'rebalance'
]
return
result
@
property
def
config
(
self
):
return
self
.
__config
[
'real-daily'
]
reports/fixed_range.py
View file @
65c7bb8d
...
@@ -31,8 +31,9 @@ class FixedRangeReport(RoboReportor):
...
@@ -31,8 +31,9 @@ class FixedRangeReport(RoboReportor):
for
range
in
self
.
range_dates
:
for
range
in
self
.
range_dates
:
start
=
filter_weekend
(
range
[
'start'
])
start
=
filter_weekend
(
range
[
'start'
])
end
=
filter_weekend
(
range
[
'end'
])
end
=
filter_weekend
(
range
[
'end'
])
row_name
=
f
"{format_date(start)}~{format_date(end)}"
if
not
datas
[
start
:
end
]
.
empty
:
result
.
loc
[
row_name
]
=
datas
[
start
:
end
]
.
values
[
-
1
]
/
datas
[
start
:
end
]
.
values
[
0
]
-
1
row_name
=
f
"{format_date(start)}~{format_date(end)}"
result
.
loc
[
row_name
]
=
datas
[
start
:
end
]
.
values
[
-
1
]
/
datas
[
start
:
end
]
.
values
[
0
]
-
1
result
=
round
(
result
,
4
)
*
100
result
=
round
(
result
,
4
)
*
100
result
.
reset_index
(
inplace
=
True
)
result
.
reset_index
(
inplace
=
True
)
result
.
rename
(
columns
=
{
'index'
:
'range-date'
},
inplace
=
True
)
result
.
rename
(
columns
=
{
'index'
:
'range-date'
},
inplace
=
True
)
...
...
reports/test_case.py
View file @
65c7bb8d
import
unittest
import
unittest
import
logging
import
logging
import
tempfile
from
datetime
import
datetime
as
dt
from
py_jftech
import
autowired
,
to_str
,
parse_date
from
py_jftech
import
autowired
,
to_str
,
parse_date
,
prev_workday
from
api
import
RoboReportor
,
RoboExportor
from
api
import
RoboReportor
,
RoboExportor
logger
=
logging
.
getLogger
(
__name__
)
logger
=
logging
.
getLogger
(
__name__
)
...
@@ -29,10 +31,13 @@ class ReportTest(unittest.TestCase):
...
@@ -29,10 +31,13 @@ class ReportTest(unittest.TestCase):
result
=
reportor
.
load_report
(
max_date
=
parse_date
(
'2022-11-01'
))
result
=
reportor
.
load_report
(
max_date
=
parse_date
(
'2022-11-01'
))
logger
.
info
(
to_str
(
result
))
logger
.
info
(
to_str
(
result
))
@
autowired
(
names
=
{
'
re
portor'
:
'backtest-export'
})
@
autowired
(
names
=
{
'
ex
portor'
:
'backtest-export'
})
def
test_backtest_export
(
self
,
exportor
:
RoboExportor
=
None
):
def
test_backtest_export
(
self
,
exportor
:
RoboExportor
=
None
):
path
=
exportor
.
export
(
max_date
=
parse_date
(
'2022-11-01'
))
exportor
.
export
(
max_date
=
parse_date
(
'2022-11-01'
))
logger
.
info
(
path
)
@
autowired
(
names
=
{
'exportor'
:
'real-daily-export'
})
def
test_daliy_export
(
self
,
exportor
:
RoboExportor
=
None
):
exportor
.
export
(
max_date
=
prev_workday
(
dt
.
today
()))
if
__name__
==
'__main__'
:
if
__name__
==
'__main__'
:
...
...
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