mirror of
https://github.com/microsoft/qlib.git
synced 2026-07-14 08:16:54 +08:00
init commit
This commit is contained in:
11
qlib/contrib/report/__init__.py
Normal file
11
qlib/contrib/report/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
GRAPH_NAME_LISt = [
|
||||
"analysis_position.report_graph",
|
||||
"analysis_position.score_ic_graph",
|
||||
"analysis_position.cumulative_return_graph",
|
||||
"analysis_position.risk_analysis_graph",
|
||||
"analysis_position.rank_label_graph",
|
||||
"analysis_model.model_performance_graph",
|
||||
]
|
||||
4
qlib/contrib/report/analysis_model/__init__.py
Normal file
4
qlib/contrib/report/analysis_model/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
from .analysis_model_performance import model_performance_graph
|
||||
304
qlib/contrib/report/analysis_model/analysis_model_performance.py
Normal file
304
qlib/contrib/report/analysis_model/analysis_model_performance.py
Normal file
@@ -0,0 +1,304 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
import pandas as pd
|
||||
|
||||
import plotly.tools as tls
|
||||
import plotly.graph_objs as go
|
||||
|
||||
import statsmodels.api as sm
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from scipy import stats
|
||||
|
||||
from ..graph import ScatterGraph, SubplotsGraph, BarGraph, HeatmapGraph
|
||||
|
||||
|
||||
def _group_return(
|
||||
pred_label: pd.DataFrame = None, reverse: bool = False, N: int = 5, **kwargs
|
||||
) -> tuple:
|
||||
"""
|
||||
|
||||
:param pred_label:
|
||||
:param reverse:
|
||||
:param N:
|
||||
:return:
|
||||
"""
|
||||
if reverse:
|
||||
pred_label["score"] *= -1
|
||||
|
||||
pred_label = pred_label.sort_values("score", ascending=False)
|
||||
|
||||
# Group1 ~ Group5 only consider the dropna values
|
||||
pred_label_drop = pred_label.dropna(subset=["score"])
|
||||
|
||||
# Group
|
||||
t_df = pd.DataFrame(
|
||||
{
|
||||
"Group-%d"
|
||||
% (i + 1): pred_label_drop.groupby(level="datetime")["label"].apply(
|
||||
lambda x: x[len(x) // N * i : len(x) // N * (i + 1)].mean()
|
||||
)
|
||||
for i in range(N)
|
||||
}
|
||||
)
|
||||
t_df.index = pd.to_datetime(t_df.index)
|
||||
|
||||
# Long-Short
|
||||
t_df["long-short"] = t_df["Group-1"] - t_df["Group-%d" % N]
|
||||
|
||||
# Long-Average
|
||||
t_df["long-average"] = (
|
||||
t_df["Group-1"] - pred_label.groupby(level="datetime")["label"].mean()
|
||||
)
|
||||
|
||||
t_df = t_df.dropna(how="all") # for days which does not contain label
|
||||
# FIXME: support HIGH-FREQ
|
||||
t_df.index = t_df.index.strftime("%Y-%m-%d")
|
||||
# Cumulative Return By Group
|
||||
group_scatter_figure = ScatterGraph(
|
||||
t_df.cumsum(),
|
||||
layout=dict(
|
||||
title="Cumulative Return", xaxis=dict(type="category", tickangle=45)
|
||||
),
|
||||
).figure
|
||||
|
||||
t_df = t_df.loc[:, ["long-short", "long-average"]]
|
||||
_bin_size = ((t_df.max() - t_df.min()) / 20).min()
|
||||
group_hist_figure = SubplotsGraph(
|
||||
t_df,
|
||||
kind_map=dict(kind="DistplotGraph", kwargs=dict(bin_size=_bin_size)),
|
||||
subplots_kwargs=dict(
|
||||
rows=1,
|
||||
cols=2,
|
||||
print_grid=False,
|
||||
subplot_titles=["long-short", "long-average"],
|
||||
),
|
||||
).figure
|
||||
|
||||
return group_scatter_figure, group_hist_figure
|
||||
|
||||
|
||||
def _plot_qq(data: pd.Series = None, dist=stats.norm) -> go.Figure:
|
||||
"""
|
||||
|
||||
:param data:
|
||||
:param dist:
|
||||
:return:
|
||||
"""
|
||||
fig, ax = plt.subplots(figsize=(8, 5))
|
||||
_mpl_fig = sm.qqplot(data.dropna(), dist, fit=True, line="45", ax=ax)
|
||||
return tls.mpl_to_plotly(_mpl_fig)
|
||||
|
||||
|
||||
def _pred_ic(pred_label: pd.DataFrame = None, rank: bool = False, **kwargs) -> tuple:
|
||||
"""
|
||||
|
||||
:param pred_label:
|
||||
:param rank:
|
||||
:return:
|
||||
"""
|
||||
if rank:
|
||||
ic = pred_label.groupby(level="datetime").apply(
|
||||
lambda x: x["label"].rank(pct=True).corr(x["score"].rank(pct=True))
|
||||
)
|
||||
else:
|
||||
ic = pred_label.groupby(level="datetime").apply(
|
||||
lambda x: x["label"].corr(x["score"])
|
||||
)
|
||||
|
||||
_index = (
|
||||
ic.index.get_level_values(0).astype("str").str.replace("-", "").str.slice(0, 6)
|
||||
)
|
||||
_monthly_ic = ic.groupby(_index).mean()
|
||||
_monthly_ic.index = pd.MultiIndex.from_arrays(
|
||||
[_monthly_ic.index.str.slice(0, 4), _monthly_ic.index.str.slice(4, 6)],
|
||||
names=["year", "month"],
|
||||
)
|
||||
|
||||
# fill month
|
||||
_month_list = pd.date_range(
|
||||
start=pd.Timestamp(f"{_index.min()[:4]}0101"),
|
||||
end=pd.Timestamp(f"{_index.max()[:4]}1231"),
|
||||
freq="1M",
|
||||
)
|
||||
_years = []
|
||||
_month = []
|
||||
for _date in _month_list:
|
||||
_date = _date.strftime("%Y%m%d")
|
||||
_years.append(_date[:4])
|
||||
_month.append(_date[4:6])
|
||||
|
||||
fill_index = pd.MultiIndex.from_arrays([_years, _month], names=["year", "month"])
|
||||
|
||||
_monthly_ic = _monthly_ic.reindex(fill_index)
|
||||
|
||||
_ic_df = ic.to_frame("ic")
|
||||
ic_bar_figure = ic_figure(_ic_df, kwargs.get("show_nature_day", True))
|
||||
|
||||
ic_heatmap_figure = HeatmapGraph(
|
||||
_monthly_ic.unstack(),
|
||||
layout=dict(title="Monthly IC", yaxis=dict(tickformat=",d")),
|
||||
graph_kwargs=dict(xtype="array", ytype="array"),
|
||||
).figure
|
||||
|
||||
dist = stats.norm
|
||||
_qqplot_fig = _plot_qq(ic, dist)
|
||||
|
||||
if isinstance(dist, stats.norm.__class__):
|
||||
dist_name = "Normal"
|
||||
else:
|
||||
dist_name = "Unknown"
|
||||
|
||||
_bin_size = ((_ic_df.max() - _ic_df.min()) / 20).min()
|
||||
_sub_graph_data = [
|
||||
(
|
||||
"ic",
|
||||
dict(
|
||||
row=1,
|
||||
col=1,
|
||||
name="",
|
||||
kind="DistplotGraph",
|
||||
graph_kwargs=dict(bin_size=_bin_size),
|
||||
),
|
||||
),
|
||||
(_qqplot_fig, dict(row=1, col=2)),
|
||||
]
|
||||
ic_hist_figure = SubplotsGraph(
|
||||
_ic_df.dropna(),
|
||||
kind_map=dict(kind="HistogramGraph", kwargs=dict()),
|
||||
subplots_kwargs=dict(
|
||||
rows=1,
|
||||
cols=2,
|
||||
print_grid=False,
|
||||
subplot_titles=["IC", "IC %s Dist. Q-Q" % dist_name],
|
||||
),
|
||||
sub_graph_data=_sub_graph_data,
|
||||
layout=dict(
|
||||
yaxis2=dict(title="Observed Quantile"),
|
||||
xaxis2=dict(title=f"{dist_name} Distribution Quantile"),
|
||||
),
|
||||
).figure
|
||||
|
||||
return ic_bar_figure, ic_heatmap_figure, ic_hist_figure
|
||||
|
||||
|
||||
def _pred_autocorr(pred_label: pd.DataFrame, lag=1, **kwargs) -> tuple:
|
||||
pred = pred_label.copy()
|
||||
pred["score_last"] = pred.groupby(level="instrument")["score"].shift(lag)
|
||||
ac = pred.groupby(level="datetime").apply(
|
||||
lambda x: x["score"].rank(pct=True).corr(x["score_last"].rank(pct=True))
|
||||
)
|
||||
# FIXME: support HIGH-FREQ
|
||||
_df = ac.to_frame("value")
|
||||
_df.index = _df.index.strftime("%Y-%m-%d")
|
||||
ac_figure = ScatterGraph(
|
||||
_df,
|
||||
layout=dict(
|
||||
title="Auto Correlation", xaxis=dict(type="category", tickangle=45)
|
||||
),
|
||||
).figure
|
||||
return (ac_figure,)
|
||||
|
||||
|
||||
def _pred_turnover(pred_label: pd.DataFrame, N=5, lag=1, **kwargs) -> tuple:
|
||||
pred = pred_label.copy()
|
||||
pred["score_last"] = pred.groupby(level="instrument")["score"].shift(lag)
|
||||
top = pred.groupby(level="datetime").apply(
|
||||
lambda x: 1
|
||||
- x.nlargest(len(x) // N, columns="score")
|
||||
.index.isin(x.nlargest(len(x) // N, columns="score_last").index)
|
||||
.sum()
|
||||
/ (len(x) // N)
|
||||
)
|
||||
bottom = pred.groupby(level="datetime").apply(
|
||||
lambda x: 1
|
||||
- x.nsmallest(len(x) // N, columns="score")
|
||||
.index.isin(x.nsmallest(len(x) // N, columns="score_last").index)
|
||||
.sum()
|
||||
/ (len(x) // N)
|
||||
)
|
||||
r_df = pd.DataFrame({"Top": top, "Bottom": bottom,})
|
||||
# FIXME: support HIGH-FREQ
|
||||
r_df.index = r_df.index.strftime("%Y-%m-%d")
|
||||
turnover_figure = ScatterGraph(
|
||||
r_df,
|
||||
layout=dict(
|
||||
title="Top-Bottom Turnover", xaxis=dict(type="category", tickangle=45)
|
||||
),
|
||||
).figure
|
||||
return (turnover_figure,)
|
||||
|
||||
|
||||
def ic_figure(ic_df: pd.DataFrame, show_nature_day=True, **kwargs) -> go.Figure:
|
||||
"""IC figure
|
||||
|
||||
:param ic_df: ic DataFrame
|
||||
:param show_nature_day: whether to display the abscissa of non-trading day
|
||||
:return: plotly.graph_objs.Figure
|
||||
"""
|
||||
if show_nature_day:
|
||||
date_index = pd.date_range(ic_df.index.min(), ic_df.index.max())
|
||||
ic_df = ic_df.reindex(date_index)
|
||||
# FIXME: support HIGH-FREQ
|
||||
ic_df.index = ic_df.index.strftime("%Y-%m-%d")
|
||||
ic_bar_figure = BarGraph(
|
||||
ic_df,
|
||||
layout=dict(
|
||||
title="Information Coefficient (IC)",
|
||||
xaxis=dict(type="category", tickangle=45),
|
||||
),
|
||||
).figure
|
||||
return ic_bar_figure
|
||||
|
||||
|
||||
def model_performance_graph(
|
||||
pred_label: pd.DataFrame,
|
||||
lag: int = 1,
|
||||
N: int = 5,
|
||||
reverse=False,
|
||||
rank=False,
|
||||
graph_names: list = ["group_return", "pred_ic", "pred_autocorr"],
|
||||
show_notebook: bool = True,
|
||||
show_nature_day=True,
|
||||
) -> [list, tuple]:
|
||||
"""Model performance
|
||||
|
||||
:param pred_label: index is **pd.MultiIndex**, index name is **[instrument, datetime]**; columns names is **[score, label]**
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
instrument datetime score label
|
||||
SH600004 2017-12-11 -0.013502 -0.013502
|
||||
2017-12-12 -0.072367 -0.072367
|
||||
2017-12-13 -0.068605 -0.068605
|
||||
2017-12-14 0.012440 0.012440
|
||||
2017-12-15 -0.102778 -0.102778
|
||||
|
||||
|
||||
:param lag: `pred.groupby(level='instrument')['score'].shift(lag)`. It will be only used in the auto-correlation computing.
|
||||
:param N: group number, default 5
|
||||
:param reverse: if `True`, `pred['score'] *= -1`
|
||||
:param rank: if **True**, calculate rank ic
|
||||
:param graph_names: graph names; default ['cumulative_return', 'pred_ic', 'pred_autocorr', 'pred_turnover']
|
||||
:param show_notebook: whether to display graphics in notebook, the default is `True`
|
||||
:param show_nature_day: whether to display the abscissa of non-trading day
|
||||
:return: if show_notebook is True, display in notebook; else return `plotly.graph_objs.Figure` list
|
||||
"""
|
||||
figure_list = []
|
||||
for graph_name in graph_names:
|
||||
fun_res = eval(f"_{graph_name}")(
|
||||
pred_label=pred_label,
|
||||
lag=lag,
|
||||
N=N,
|
||||
reverse=reverse,
|
||||
rank=rank,
|
||||
show_nature_day=show_nature_day,
|
||||
)
|
||||
figure_list += fun_res
|
||||
|
||||
if show_notebook:
|
||||
BarGraph.show_graph_in_notebook(figure_list)
|
||||
else:
|
||||
return figure_list
|
||||
8
qlib/contrib/report/analysis_position/__init__.py
Normal file
8
qlib/contrib/report/analysis_position/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
from .cumulative_return import cumulative_return_graph
|
||||
from .score_ic import score_ic_graph
|
||||
from .report import report_graph
|
||||
from .rank_label import rank_label_graph
|
||||
from .risk_analysis import risk_analysis_graph
|
||||
281
qlib/contrib/report/analysis_position/cumulative_return.py
Normal file
281
qlib/contrib/report/analysis_position/cumulative_return.py
Normal file
@@ -0,0 +1,281 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
import copy
|
||||
from typing import Iterable
|
||||
|
||||
import pandas as pd
|
||||
import plotly.graph_objs as go
|
||||
|
||||
from ..graph import BaseGraph, SubplotsGraph
|
||||
|
||||
from ..analysis_position.parse_position import get_position_data
|
||||
|
||||
|
||||
def _get_cum_return_data_with_position(
|
||||
position: dict,
|
||||
report_normal: pd.DataFrame,
|
||||
label_data: pd.DataFrame,
|
||||
start_date=None,
|
||||
end_date=None,
|
||||
):
|
||||
"""
|
||||
|
||||
:param position:
|
||||
:param report_normal:
|
||||
:param label_data:
|
||||
:param start_date:
|
||||
:param end_date:
|
||||
:return:
|
||||
"""
|
||||
_cumulative_return_df = get_position_data(
|
||||
position=position,
|
||||
report_normal=report_normal,
|
||||
label_data=label_data,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
).copy()
|
||||
|
||||
_cumulative_return_df["label"] = (
|
||||
_cumulative_return_df["label"] - _cumulative_return_df["bench"]
|
||||
)
|
||||
_cumulative_return_df = _cumulative_return_df.dropna()
|
||||
df_gp = _cumulative_return_df.groupby(level="datetime")
|
||||
result_list = []
|
||||
for gp in df_gp:
|
||||
date = gp[0]
|
||||
day_df = gp[1]
|
||||
|
||||
_hold_df = day_df[day_df["status"] == 0]
|
||||
_buy_df = day_df[day_df["status"] == 1]
|
||||
_sell_df = day_df[day_df["status"] == -1]
|
||||
|
||||
hold_value = (_hold_df["label"] * _hold_df["weight"]).sum()
|
||||
hold_weight = _hold_df["weight"].sum()
|
||||
hold_mean = (hold_value / hold_weight) if hold_weight else 0
|
||||
|
||||
sell_value = (_sell_df["label"] * _sell_df["weight"]).sum()
|
||||
sell_weight = _sell_df["weight"].sum()
|
||||
sell_mean = (sell_value / sell_weight) if sell_weight else 0
|
||||
|
||||
buy_value = (_buy_df["label"] * _buy_df["weight"]).sum()
|
||||
buy_weight = _buy_df["weight"].sum()
|
||||
buy_mean = (buy_value / buy_weight) if buy_weight else 0
|
||||
|
||||
result_list.append(
|
||||
dict(
|
||||
hold_value=hold_value,
|
||||
hold_mean=hold_mean,
|
||||
hold_weight=hold_weight,
|
||||
buy_value=buy_value,
|
||||
buy_mean=buy_mean,
|
||||
buy_weight=buy_weight,
|
||||
sell_value=sell_value,
|
||||
sell_mean=sell_mean,
|
||||
sell_weight=sell_weight,
|
||||
buy_minus_sell_value=buy_value - sell_value,
|
||||
buy_minus_sell_mean=buy_mean - sell_mean,
|
||||
buy_plus_sell_weight=buy_weight + sell_weight,
|
||||
date=date,
|
||||
)
|
||||
)
|
||||
|
||||
r_df = pd.DataFrame(data=result_list)
|
||||
r_df["cum_hold"] = r_df["hold_mean"].cumsum()
|
||||
r_df["cum_buy"] = r_df["buy_mean"].cumsum()
|
||||
r_df["cum_sell"] = r_df["sell_mean"].cumsum()
|
||||
r_df["cum_buy_minus_sell"] = r_df["buy_minus_sell_mean"].cumsum()
|
||||
return r_df
|
||||
|
||||
|
||||
def _get_figure_with_position(
|
||||
position: dict,
|
||||
report_normal: pd.DataFrame,
|
||||
label_data: pd.DataFrame,
|
||||
start_date=None,
|
||||
end_date=None,
|
||||
) -> Iterable[go.Figure]:
|
||||
"""Get average analysis figures
|
||||
|
||||
:param position: position
|
||||
:param report_normal:
|
||||
:param label_data:
|
||||
:param start_date:
|
||||
:param end_date:
|
||||
:return:
|
||||
"""
|
||||
|
||||
cum_return_df = _get_cum_return_data_with_position(
|
||||
position, report_normal, label_data, start_date, end_date
|
||||
)
|
||||
cum_return_df = cum_return_df.set_index("date")
|
||||
# FIXME: support HIGH-FREQ
|
||||
cum_return_df.index = cum_return_df.index.strftime('%Y-%m-%d')
|
||||
|
||||
# Create figures
|
||||
for _t_name in ["buy", "sell", "buy_minus_sell", "hold"]:
|
||||
sub_graph_data = [
|
||||
(
|
||||
"cum_{}".format(_t_name),
|
||||
dict(
|
||||
row=1, col=1, graph_kwargs={"mode": "lines+markers", "xaxis": "x3"}
|
||||
),
|
||||
),
|
||||
(
|
||||
"{}_weight".format(
|
||||
_t_name.replace("minus", "plus") if "minus" in _t_name else _t_name
|
||||
),
|
||||
dict(row=2, col=1),
|
||||
),
|
||||
(
|
||||
"{}_value".format(_t_name),
|
||||
dict(row=1, col=2, kind="HistogramGraph", graph_kwargs={}),
|
||||
),
|
||||
]
|
||||
|
||||
_default_xaxis = dict(showline=False, zeroline=True, tickangle=45)
|
||||
_default_yaxis = dict(zeroline=True, showline=True, showticklabels=True)
|
||||
sub_graph_layout = dict(
|
||||
xaxis1=dict(**_default_xaxis, type="category", showticklabels=False),
|
||||
xaxis3=dict(**_default_xaxis, type="category"),
|
||||
xaxis2=_default_xaxis,
|
||||
yaxis1=dict(**_default_yaxis, title=_t_name),
|
||||
yaxis2=_default_yaxis,
|
||||
yaxis3=_default_yaxis,
|
||||
)
|
||||
|
||||
mean_value = cum_return_df["{}_value".format(_t_name)].mean()
|
||||
layout = dict(
|
||||
height=500,
|
||||
title=f"{_t_name}(the red line in the histogram on the right represents the average)",
|
||||
shapes=[
|
||||
{
|
||||
"type": "line",
|
||||
"xref": "x2",
|
||||
"yref": "paper",
|
||||
"x0": mean_value,
|
||||
"y0": 0,
|
||||
"x1": mean_value,
|
||||
"y1": 1,
|
||||
# NOTE: 'fillcolor': '#d3d3d3', 'opacity': 0.3,
|
||||
"line": {"color": "red", "width": 1},
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
kind_map = dict(kind="ScatterGraph", kwargs=dict(mode="lines+markers"))
|
||||
specs = [
|
||||
[{"rowspan": 1}, {"rowspan": 2}],
|
||||
[{"rowspan": 1}, None],
|
||||
]
|
||||
subplots_kwargs = dict(
|
||||
vertical_spacing=0.01,
|
||||
rows=2,
|
||||
cols=2,
|
||||
row_width=[1, 2],
|
||||
column_width=[3, 1],
|
||||
print_grid=False,
|
||||
specs=specs,
|
||||
)
|
||||
yield SubplotsGraph(
|
||||
cum_return_df,
|
||||
layout=layout,
|
||||
kind_map=kind_map,
|
||||
sub_graph_layout=sub_graph_layout,
|
||||
sub_graph_data=sub_graph_data,
|
||||
subplots_kwargs=subplots_kwargs,
|
||||
).figure
|
||||
|
||||
|
||||
def cumulative_return_graph(
|
||||
position: dict,
|
||||
report_normal: pd.DataFrame,
|
||||
label_data: pd.DataFrame,
|
||||
show_notebook=True,
|
||||
start_date=None,
|
||||
end_date=None,
|
||||
) -> Iterable[go.Figure]:
|
||||
"""Backtest buy, sell, and holding cumulative return graph
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from qlib.data import D
|
||||
from qlib.contrib.evaluate import risk_analysis, backtest, long_short_backtest
|
||||
from qlib.contrib.strategy import TopkDropoutStrategy
|
||||
|
||||
# backtest parameters
|
||||
bparas = {}
|
||||
bparas['limit_threshold'] = 0.095
|
||||
bparas['account'] = 1000000000
|
||||
|
||||
sparas = {}
|
||||
sparas['topk'] = 50
|
||||
sparas['n_drop'] = 5
|
||||
strategy = TopkDropoutStrategy(**sparas)
|
||||
|
||||
report_normal_df, positions = backtest(pred_df, strategy, **bparas)
|
||||
|
||||
pred_df_dates = pred_df.index.get_level_values(level='datetime')
|
||||
features_df = D.features(D.instruments('csi500'), ['Ref($close, -1)/$close - 1'], pred_df_dates.min(), pred_df_dates.max())
|
||||
features_df.columns = ['label']
|
||||
|
||||
qcr.cumulative_return_graph(positions, report_normal_df, features_df)
|
||||
|
||||
|
||||
Graph desc:
|
||||
- Axis X: Trading day
|
||||
- Axis Y:
|
||||
- Above axis Y: (((Ref($close, -1)/$close - 1) * weight).sum() / weight.sum()).cumsum()
|
||||
- Below axis Y: Daily weight sum
|
||||
- In the sell graph, y < 0 stands for profit; in other cases, y > 0 stands for profit.
|
||||
- In the buy_minus_sell graph, the y value of the weight graph at the bottom is buy_weight + sell_weight.
|
||||
- In each graph, the red line in the histogram on the right represents the average.
|
||||
|
||||
:param position: position data
|
||||
:param report_normal:
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
return cost bench turnover
|
||||
date
|
||||
2017-01-04 0.003421 0.000864 0.011693 0.576325
|
||||
2017-01-05 0.000508 0.000447 0.000721 0.227882
|
||||
2017-01-06 -0.003321 0.000212 -0.004322 0.102765
|
||||
2017-01-09 0.006753 0.000212 0.006874 0.105864
|
||||
2017-01-10 -0.000416 0.000440 -0.003350 0.208396
|
||||
|
||||
|
||||
:param label_data: `D.features` result; index is `pd.MultiIndex`, index name is [`instrument`, `datetime`]; columns names is [`label`].
|
||||
**The ``label`` T is the change from T to T+1**, it is recommended to use ``close``, example: D.features(D.instruments('csi500'), ['Ref($close, -1)/$close-1'])
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
label
|
||||
instrument datetime
|
||||
SH600004 2017-12-11 -0.013502
|
||||
2017-12-12 -0.072367
|
||||
2017-12-13 -0.068605
|
||||
2017-12-14 0.012440
|
||||
2017-12-15 -0.102778
|
||||
|
||||
|
||||
:param show_notebook: True or False. If True, show graph in notebook, else return figures
|
||||
:param start_date: start date
|
||||
:param end_date: end date
|
||||
:return:
|
||||
"""
|
||||
position = copy.deepcopy(position)
|
||||
report_normal = report_normal.copy()
|
||||
label_data.columns = ["label"]
|
||||
_figures = _get_figure_with_position(
|
||||
position, report_normal, label_data, start_date, end_date
|
||||
)
|
||||
if show_notebook:
|
||||
BaseGraph.show_graph_in_notebook(_figures)
|
||||
else:
|
||||
return _figures
|
||||
187
qlib/contrib/report/analysis_position/parse_position.py
Normal file
187
qlib/contrib/report/analysis_position/parse_position.py
Normal file
@@ -0,0 +1,187 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
import pandas as pd
|
||||
|
||||
|
||||
from ...backtest.profit_attribution import get_stock_weight_df
|
||||
|
||||
|
||||
def parse_position(position: dict = None) -> pd.DataFrame:
|
||||
"""Parse position dict to position DataFrame
|
||||
|
||||
:param position: position data
|
||||
:return: position DataFrame;
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
position_df = parse_position(positions)
|
||||
print(position_df.head())
|
||||
# status: 0-hold, -1-sell, 1-buy
|
||||
|
||||
amount cash count price status weight
|
||||
instrument datetime
|
||||
SZ000547 2017-01-04 44.154290 211405.285654 1 205.189575 1 0.031255
|
||||
SZ300202 2017-01-04 60.638845 211405.285654 1 154.356506 1 0.032290
|
||||
SH600158 2017-01-04 46.531681 211405.285654 1 153.895142 1 0.024704
|
||||
SH600545 2017-01-04 197.173093 211405.285654 1 48.607037 1 0.033063
|
||||
SZ000930 2017-01-04 103.938300 211405.285654 1 80.759453 1 0.028958
|
||||
|
||||
|
||||
"""
|
||||
|
||||
position_weight_df = get_stock_weight_df(position)
|
||||
# If the day does not exist, use the last weight
|
||||
position_weight_df.fillna(method="ffill", inplace=True)
|
||||
|
||||
previous_data = {"date": None, "code_list": []}
|
||||
|
||||
result_df = pd.DataFrame()
|
||||
for _trading_date, _value in position.items():
|
||||
# pd_date type: pd.Timestamp
|
||||
_cash = _value.pop("cash")
|
||||
for _item in ["today_account_value"]:
|
||||
if _item in _value:
|
||||
_value.pop(_item)
|
||||
|
||||
_trading_day_df = pd.DataFrame.from_dict(_value, orient="index")
|
||||
_trading_day_df["weight"] = position_weight_df.loc[_trading_date]
|
||||
_trading_day_df["cash"] = _cash
|
||||
_trading_day_df["date"] = _trading_date
|
||||
# status: 0-hold, -1-sell, 1-buy
|
||||
_trading_day_df["status"] = 0
|
||||
|
||||
# T not exist, T-1 exist, T sell
|
||||
_cur_day_sell = set(previous_data["code_list"]) - set(_trading_day_df.index)
|
||||
# T exist, T-1 not exist, T buy
|
||||
_cur_day_buy = set(_trading_day_df.index) - set(previous_data["code_list"])
|
||||
|
||||
# Trading day buy
|
||||
_trading_day_df.loc[_trading_day_df.index.isin(_cur_day_buy), "status"] = 1
|
||||
|
||||
# Trading day sell
|
||||
if not result_df.empty:
|
||||
_trading_day_sell_df = result_df.loc[
|
||||
(result_df["date"] == previous_data["date"])
|
||||
& (result_df.index.isin(_cur_day_sell))
|
||||
].copy()
|
||||
if not _trading_day_sell_df.empty:
|
||||
_trading_day_sell_df["status"] = -1
|
||||
_trading_day_sell_df["date"] = _trading_date
|
||||
_trading_day_df = _trading_day_df.append(
|
||||
_trading_day_sell_df, sort=False
|
||||
)
|
||||
|
||||
result_df = result_df.append(_trading_day_df, sort=True)
|
||||
|
||||
previous_data = dict(
|
||||
date=_trading_date,
|
||||
code_list=_trading_day_df[_trading_day_df["status"] != -1].index,
|
||||
)
|
||||
|
||||
result_df.reset_index(inplace=True)
|
||||
result_df.rename(columns={"date": "datetime", "index": "instrument"}, inplace=True)
|
||||
return result_df.set_index(["instrument", "datetime"])
|
||||
|
||||
|
||||
def _add_label_to_position(
|
||||
position_df: pd.DataFrame, label_data: pd.DataFrame
|
||||
) -> pd.DataFrame:
|
||||
"""Concat position with custom label
|
||||
|
||||
:param position_df: position DataFrame
|
||||
:param label_data:
|
||||
:return: concat result
|
||||
"""
|
||||
|
||||
_start_time = position_df.index.get_level_values(level="datetime").min()
|
||||
_end_time = position_df.index.get_level_values(level="datetime").max()
|
||||
label_data = label_data.loc(axis=0)[:, pd.to_datetime(_start_time) :]
|
||||
_result_df = pd.concat([position_df, label_data], axis=1, sort=True).reindex(
|
||||
label_data.index
|
||||
)
|
||||
_result_df = _result_df.loc[_result_df.index.get_level_values(1) <= _end_time]
|
||||
return _result_df
|
||||
|
||||
|
||||
def _add_bench_to_position(
|
||||
position_df: pd.DataFrame = None, bench: pd.Series = None
|
||||
) -> pd.DataFrame:
|
||||
"""Concat position with bench
|
||||
|
||||
:param position_df: position DataFrame
|
||||
:param bench: report normal data
|
||||
:return: concat result
|
||||
"""
|
||||
_temp_df = position_df.reset_index(level="instrument")
|
||||
# FIXME: After the stock is bought and sold, the rise and fall of the next trading day are calculated.
|
||||
_temp_df["bench"] = bench.shift(-1)
|
||||
res_df = _temp_df.set_index(["instrument", _temp_df.index])
|
||||
return res_df
|
||||
|
||||
|
||||
def _calculate_label_rank(df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""calculate label rank
|
||||
|
||||
:param df:
|
||||
:return:
|
||||
"""
|
||||
_label_name = "label"
|
||||
|
||||
def _calculate_day_value(g_df: pd.DataFrame):
|
||||
g_df = g_df.copy()
|
||||
g_df["rank_ratio"] = g_df[_label_name].rank(ascending=False) / len(g_df) * 100
|
||||
|
||||
# Sell: -1, Hold: 0, Buy: 1
|
||||
for i in [-1, 0, 1]:
|
||||
g_df.loc[g_df["status"] == i, "rank_label_mean"] = g_df[
|
||||
g_df["status"] == i
|
||||
]["rank_ratio"].mean()
|
||||
|
||||
g_df["excess_return"] = g_df[_label_name] - g_df[_label_name].mean()
|
||||
return g_df
|
||||
|
||||
return df.groupby(level="datetime").apply(_calculate_day_value)
|
||||
|
||||
|
||||
def get_position_data(
|
||||
position: dict,
|
||||
label_data: pd.DataFrame,
|
||||
report_normal: pd.DataFrame = None,
|
||||
calculate_label_rank=False,
|
||||
start_date=None,
|
||||
end_date=None,
|
||||
) -> pd.DataFrame:
|
||||
"""Concat position data with pred/report_normal
|
||||
|
||||
:param position: position data
|
||||
:param report_normal: report normal, must be container 'bench' column
|
||||
:param label_data:
|
||||
:param calculate_label_rank:
|
||||
:param start_date: start date
|
||||
:param end_date: end date
|
||||
:return: concat result,
|
||||
columns: ['amount', 'cash', 'count', 'price', 'status', 'weight', 'label',
|
||||
'rank_ratio', 'rank_label_mean', 'excess_return', 'score', 'bench']
|
||||
index: ['instrument', 'date']
|
||||
"""
|
||||
_position_df = parse_position(position)
|
||||
|
||||
# Add custom_label, rank_ratio, rank_mean, and excess_return field
|
||||
_position_df = _add_label_to_position(_position_df, label_data)
|
||||
|
||||
if calculate_label_rank:
|
||||
_position_df = _calculate_label_rank(_position_df)
|
||||
|
||||
if report_normal is not None:
|
||||
# Add bench field
|
||||
_position_df = _add_bench_to_position(_position_df, report_normal["bench"])
|
||||
|
||||
_date_list = _position_df.index.get_level_values(level="datetime")
|
||||
start_date = _date_list.min() if start_date is None else start_date
|
||||
end_date = _date_list.max() if end_date is None else end_date
|
||||
_position_df = _position_df.loc[
|
||||
(start_date <= _date_list) & (_date_list <= end_date)
|
||||
]
|
||||
return _position_df
|
||||
127
qlib/contrib/report/analysis_position/rank_label.py
Normal file
127
qlib/contrib/report/analysis_position/rank_label.py
Normal file
@@ -0,0 +1,127 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
import copy
|
||||
from typing import Iterable
|
||||
|
||||
import pandas as pd
|
||||
import plotly.graph_objs as go
|
||||
|
||||
from ..graph import ScatterGraph
|
||||
from ..analysis_position.parse_position import get_position_data
|
||||
|
||||
|
||||
def _get_figure_with_position(
|
||||
position: dict, label_data: pd.DataFrame, start_date=None, end_date=None
|
||||
) -> Iterable[go.Figure]:
|
||||
"""Get average analysis figures
|
||||
|
||||
:param position: position
|
||||
:param label_data:
|
||||
:param start_date:
|
||||
:param end_date:
|
||||
:return:
|
||||
"""
|
||||
_position_df = get_position_data(
|
||||
position,
|
||||
label_data,
|
||||
calculate_label_rank=True,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
)
|
||||
|
||||
res_dict = dict()
|
||||
_pos_gp = _position_df.groupby(level=1)
|
||||
for _item in _pos_gp:
|
||||
_date = _item[0]
|
||||
_day_df = _item[1]
|
||||
|
||||
_day_value = res_dict.setdefault(_date, {})
|
||||
for _i, _name in {0: "Hold", 1: "Buy", -1: "Sell"}.items():
|
||||
_temp_df = _day_df[_day_df["status"] == _i]
|
||||
if _temp_df.empty:
|
||||
_day_value[_name] = 0
|
||||
else:
|
||||
_day_value[_name] = _temp_df["rank_label_mean"].values[0]
|
||||
|
||||
_res_df = pd.DataFrame.from_dict(res_dict, orient="index")
|
||||
# FIXME: support HIGH-FREQ
|
||||
_res_df.index = _res_df.index.strftime('%Y-%m-%d')
|
||||
for _col in _res_df.columns:
|
||||
yield ScatterGraph(
|
||||
_res_df.loc[:, [_col]],
|
||||
layout=dict(
|
||||
title=_col,
|
||||
xaxis=dict(type="category", tickangle=45),
|
||||
yaxis=dict(title="lable-rank-ratio: %"),
|
||||
),
|
||||
graph_kwargs=dict(mode="lines+markers"),
|
||||
).figure
|
||||
|
||||
|
||||
def rank_label_graph(
|
||||
position: dict,
|
||||
label_data: pd.DataFrame,
|
||||
start_date=None,
|
||||
end_date=None,
|
||||
show_notebook=True,
|
||||
) -> Iterable[go.Figure]:
|
||||
"""Ranking percentage of stocks buy, sell, and holding on the trading day.
|
||||
Average rank-ratio(similar to **sell_df['label'].rank(ascending=False) / len(sell_df)**) of daily trading
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from qlib.data import D
|
||||
from qlib.contrib.evaluate import backtest
|
||||
from qlib.contrib.strategy import TopkDropoutStrategy
|
||||
|
||||
# backtest parameters
|
||||
bparas = {}
|
||||
bparas['limit_threshold'] = 0.095
|
||||
bparas['account'] = 1000000000
|
||||
|
||||
sparas = {}
|
||||
sparas['topk'] = 50
|
||||
sparas['n_drop'] = 230
|
||||
strategy = TopkDropoutStrategy(**sparas)
|
||||
|
||||
_, positions = backtest(pred_df, strategy, **bparas)
|
||||
|
||||
pred_df_dates = pred_df.index.get_level_values(level='datetime')
|
||||
features_df = D.features(D.instruments('csi500'), ['Ref($close, -1)/$close-1'], pred_df_dates.min(), pred_df_dates.max())
|
||||
features_df.columns = ['label']
|
||||
|
||||
qcr.rank_label_graph(positions, features_df, pred_df_dates.min(), pred_df_dates.max())
|
||||
|
||||
|
||||
:param position: position data; **qlib.contrib.backtest.backtest.backtest** result
|
||||
:param label_data: **D.features** result; index is **pd.MultiIndex**, index name is **[instrument, datetime]**; columns names is **[label]**.
|
||||
**The ``label`` T is the change from T to T+1**, it is recommended to use ``close``, example: D.features(D.instruments('csi500'), ['Ref($close, -1)/$close-1'])
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
label
|
||||
instrument datetime
|
||||
SH600004 2017-12-11 -0.013502
|
||||
2017-12-12 -0.072367
|
||||
2017-12-13 -0.068605
|
||||
2017-12-14 0.012440
|
||||
2017-12-15 -0.102778
|
||||
|
||||
|
||||
:param start_date: start date
|
||||
:param end_date: end_date
|
||||
:param show_notebook: **True** or **False**. If True, show graph in notebook, else return figures
|
||||
:return:
|
||||
"""
|
||||
position = copy.deepcopy(position)
|
||||
label_data.columns = ["label"]
|
||||
_figures = _get_figure_with_position(position, label_data, start_date, end_date)
|
||||
if show_notebook:
|
||||
ScatterGraph.show_graph_in_notebook(_figures)
|
||||
else:
|
||||
return _figures
|
||||
220
qlib/contrib/report/analysis_position/report.py
Normal file
220
qlib/contrib/report/analysis_position/report.py
Normal file
@@ -0,0 +1,220 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from ..graph import SubplotsGraph, BaseGraph
|
||||
|
||||
|
||||
def _calculate_maximum(df: pd.DataFrame, is_ex: bool = False):
|
||||
"""
|
||||
|
||||
:param df:
|
||||
:param is_ex:
|
||||
:return:
|
||||
"""
|
||||
if is_ex:
|
||||
end_date = df["cum_ex_return_wo_cost_mdd"].idxmin()
|
||||
start_date = df.loc[df.index <= end_date]["cum_ex_return_wo_cost"].idxmax()
|
||||
else:
|
||||
end_date = df["return_wo_mdd"].idxmin()
|
||||
start_date = df.loc[df.index <= end_date]["cum_return_wo_cost"].idxmax()
|
||||
return start_date, end_date
|
||||
|
||||
|
||||
def _calculate_mdd(series):
|
||||
"""
|
||||
Calculate mdd
|
||||
|
||||
:param series:
|
||||
:return:
|
||||
"""
|
||||
return series - series.cummax()
|
||||
|
||||
|
||||
def _calculate_report_data(df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
|
||||
:param df:
|
||||
:return:
|
||||
"""
|
||||
|
||||
df.index = df.index.strftime("%Y-%m-%d")
|
||||
|
||||
report_df = pd.DataFrame()
|
||||
|
||||
report_df["cum_bench"] = df["bench"].cumsum()
|
||||
report_df["cum_return_wo_cost"] = df["return"].cumsum()
|
||||
report_df["cum_return_w_cost"] = (df["return"] - df["cost"]).cumsum()
|
||||
# report_df['cum_return'] - report_df['cum_return'].cummax()
|
||||
report_df["return_wo_mdd"] = _calculate_mdd(report_df["cum_return_wo_cost"])
|
||||
report_df["return_w_cost_mdd"] = _calculate_mdd(
|
||||
(df["return"] - df["cost"]).cumsum()
|
||||
)
|
||||
|
||||
report_df["cum_ex_return_wo_cost"] = (df["return"] - df["bench"]).cumsum()
|
||||
report_df["cum_ex_return_w_cost"] = (
|
||||
df["return"] - df["bench"] - df["cost"]
|
||||
).cumsum()
|
||||
report_df["cum_ex_return_wo_cost_mdd"] = _calculate_mdd(
|
||||
(df["return"] - df["bench"]).cumsum()
|
||||
)
|
||||
report_df["cum_ex_return_w_cost_mdd"] = _calculate_mdd(
|
||||
(df["return"] - df["cost"] - df["bench"]).cumsum()
|
||||
)
|
||||
# return_wo_mdd , return_w_cost_mdd, cum_ex_return_wo_cost_mdd, cum_ex_return_w
|
||||
|
||||
report_df["turnover"] = df["turnover"]
|
||||
report_df.sort_index(ascending=True, inplace=True)
|
||||
return report_df
|
||||
|
||||
|
||||
def _report_figure(df: pd.DataFrame) -> [list, tuple]:
|
||||
"""
|
||||
|
||||
:param df:
|
||||
:return:
|
||||
"""
|
||||
|
||||
# Get data
|
||||
report_df = _calculate_report_data(df)
|
||||
|
||||
# Maximum Drawdown
|
||||
max_start_date, max_end_date = _calculate_maximum(report_df)
|
||||
ex_max_start_date, ex_max_end_date = _calculate_maximum(report_df, True)
|
||||
|
||||
_temp_df = report_df.reset_index()
|
||||
_temp_df.loc[-1] = 0
|
||||
_temp_df = _temp_df.shift(1)
|
||||
_temp_df.loc[0, "index"] = "T0"
|
||||
_temp_df.set_index("index", inplace=True)
|
||||
_temp_df.iloc[0] = 0
|
||||
report_df = _temp_df
|
||||
|
||||
# Create figure
|
||||
_default_kind_map = dict(kind="ScatterGraph", kwargs={"mode": "lines+markers"})
|
||||
_temp_fill_args = {"fill": "tozeroy", "mode": "lines+markers"}
|
||||
_column_row_col_dict = [
|
||||
("cum_bench", dict(row=1, col=1)),
|
||||
("cum_return_wo_cost", dict(row=1, col=1)),
|
||||
("cum_return_w_cost", dict(row=1, col=1)),
|
||||
("return_wo_mdd", dict(row=2, col=1, graph_kwargs=_temp_fill_args)),
|
||||
("return_w_cost_mdd", dict(row=3, col=1, graph_kwargs=_temp_fill_args)),
|
||||
("cum_ex_return_wo_cost", dict(row=4, col=1)),
|
||||
("cum_ex_return_w_cost", dict(row=4, col=1)),
|
||||
("turnover", dict(row=5, col=1)),
|
||||
("cum_ex_return_w_cost_mdd", dict(row=6, col=1, graph_kwargs=_temp_fill_args)),
|
||||
("cum_ex_return_wo_cost_mdd", dict(row=7, col=1, graph_kwargs=_temp_fill_args)),
|
||||
]
|
||||
|
||||
_subplot_layout = dict(
|
||||
xaxis=dict(showline=True, type="category", tickangle=45),
|
||||
yaxis=dict(zeroline=True, showline=True, showticklabels=True),
|
||||
)
|
||||
for i in range(2, 8):
|
||||
# yaxis
|
||||
_subplot_layout.update(
|
||||
{
|
||||
"yaxis{}".format(i): dict(
|
||||
zeroline=True, showline=True, showticklabels=True
|
||||
)
|
||||
}
|
||||
)
|
||||
_layout_style = dict(
|
||||
height=1200,
|
||||
title=" ",
|
||||
shapes=[
|
||||
{
|
||||
"type": "rect",
|
||||
"xref": "x",
|
||||
"yref": "paper",
|
||||
"x0": max_start_date,
|
||||
"y0": 0.55,
|
||||
"x1": max_end_date,
|
||||
"y1": 1,
|
||||
"fillcolor": "#d3d3d3",
|
||||
"opacity": 0.3,
|
||||
"line": {"width": 0,},
|
||||
},
|
||||
{
|
||||
"type": "rect",
|
||||
"xref": "x",
|
||||
"yref": "paper",
|
||||
"x0": ex_max_start_date,
|
||||
"y0": 0,
|
||||
"x1": ex_max_end_date,
|
||||
"y1": 0.55,
|
||||
"fillcolor": "#d3d3d3",
|
||||
"opacity": 0.3,
|
||||
"line": {"width": 0,},
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
_subplot_kwargs = dict(
|
||||
shared_xaxes=True,
|
||||
vertical_spacing=0.01,
|
||||
rows=7,
|
||||
cols=1,
|
||||
row_width=[1, 1, 1, 3, 1, 1, 3],
|
||||
print_grid=False,
|
||||
)
|
||||
figure = SubplotsGraph(
|
||||
df=report_df,
|
||||
layout=_layout_style,
|
||||
sub_graph_data=_column_row_col_dict,
|
||||
subplots_kwargs=_subplot_kwargs,
|
||||
kind_map=_default_kind_map,
|
||||
sub_graph_layout=_subplot_layout,
|
||||
).figure
|
||||
return (figure,)
|
||||
|
||||
|
||||
def report_graph(report_df: pd.DataFrame, show_notebook: bool = True) -> [list, tuple]:
|
||||
"""display backtest report
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from qlib.contrib.evaluate import backtest
|
||||
from qlib.contrib.strategy import TopkDropoutStrategy
|
||||
|
||||
# backtest parameters
|
||||
bparas = {}
|
||||
bparas['limit_threshold'] = 0.095
|
||||
bparas['account'] = 1000000000
|
||||
|
||||
sparas = {}
|
||||
sparas['topk'] = 50
|
||||
sparas['n_drop'] = 230
|
||||
strategy = TopkDropoutStrategy(**sparas)
|
||||
|
||||
report_normal_df, _ = backtest(pred_df, strategy, **bparas)
|
||||
|
||||
qcr.report_graph(report_normal_df)
|
||||
|
||||
:param report_df: **df.index.name** must be **date**, **df.columns** must contain **return**, **turnover**, **cost**, **bench**
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
return cost bench turnover
|
||||
date
|
||||
2017-01-04 0.003421 0.000864 0.011693 0.576325
|
||||
2017-01-05 0.000508 0.000447 0.000721 0.227882
|
||||
2017-01-06 -0.003321 0.000212 -0.004322 0.102765
|
||||
2017-01-09 0.006753 0.000212 0.006874 0.105864
|
||||
2017-01-10 -0.000416 0.000440 -0.003350 0.208396
|
||||
|
||||
|
||||
:param show_notebook: whether to display graphics in notebook, the default is **True**
|
||||
:return: if show_notebook is True, display in notebook; else return **plotly.graph_objs.Figure** list
|
||||
"""
|
||||
report_df = report_df.copy()
|
||||
fig_list = _report_figure(report_df)
|
||||
if show_notebook:
|
||||
BaseGraph.show_graph_in_notebook(fig_list)
|
||||
else:
|
||||
return fig_list
|
||||
271
qlib/contrib/report/analysis_position/risk_analysis.py
Normal file
271
qlib/contrib/report/analysis_position/risk_analysis.py
Normal file
@@ -0,0 +1,271 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
from typing import Iterable
|
||||
|
||||
import pandas as pd
|
||||
|
||||
import plotly.graph_objs as py
|
||||
|
||||
from ...evaluate import risk_analysis
|
||||
|
||||
from ..graph import SubplotsGraph, ScatterGraph
|
||||
|
||||
|
||||
def _get_risk_analysis_data_with_report(
|
||||
report_normal_df: pd.DataFrame,
|
||||
# report_long_short_df: pd.DataFrame,
|
||||
date: pd.Timestamp,
|
||||
) -> pd.DataFrame:
|
||||
"""Get risk analysis data with report
|
||||
|
||||
:param report_normal_df: report data
|
||||
:param report_long_short_df: report data
|
||||
:param date: date string
|
||||
:return:
|
||||
"""
|
||||
|
||||
analysis = dict()
|
||||
# if not report_long_short_df.empty:
|
||||
# analysis["pred_long"] = risk_analysis(report_long_short_df["long"])
|
||||
# analysis["pred_short"] = risk_analysis(report_long_short_df["short"])
|
||||
# analysis["pred_long_short"] = risk_analysis(report_long_short_df["long_short"])
|
||||
|
||||
if not report_normal_df.empty:
|
||||
analysis["sub_bench"] = risk_analysis(
|
||||
report_normal_df["return"] - report_normal_df["bench"]
|
||||
)
|
||||
analysis["sub_cost"] = risk_analysis(
|
||||
report_normal_df["return"]
|
||||
- report_normal_df["bench"]
|
||||
- report_normal_df["cost"]
|
||||
)
|
||||
analysis_df = pd.concat(analysis) # type: pd.DataFrame
|
||||
analysis_df["date"] = date
|
||||
return analysis_df
|
||||
|
||||
|
||||
def _get_all_risk_analysis(risk_df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""risk_df to standard
|
||||
|
||||
:param risk_df: risk data
|
||||
:return:
|
||||
"""
|
||||
if risk_df is None:
|
||||
return pd.DataFrame()
|
||||
risk_df = risk_df.unstack()
|
||||
risk_df.columns = risk_df.columns.droplevel(0)
|
||||
return risk_df.drop("mean", axis=1)
|
||||
|
||||
|
||||
def _get_monthly_risk_analysis_with_report(report_normal_df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""Get monthly analysis data
|
||||
|
||||
:param report_normal_df:
|
||||
# :param report_long_short_df:
|
||||
:return:
|
||||
"""
|
||||
|
||||
# Group by month
|
||||
report_normal_gp = report_normal_df.groupby(
|
||||
[report_normal_df.index.year, report_normal_df.index.month]
|
||||
)
|
||||
# report_long_short_gp = report_long_short_df.groupby(
|
||||
# [report_long_short_df.index.year, report_long_short_df.index.month]
|
||||
# )
|
||||
|
||||
gp_month = sorted(set(report_normal_gp.size().index))
|
||||
|
||||
_monthly_df = pd.DataFrame()
|
||||
for gp_m in gp_month:
|
||||
_m_report_normal = report_normal_gp.get_group(gp_m)
|
||||
# _m_report_long_short = report_long_short_gp.get_group(gp_m)
|
||||
|
||||
if len(_m_report_normal) < 3:
|
||||
# The month's data is less than 3, not displayed
|
||||
# FIXME: If the trading day of a month is less than 3 days, a breakpoint will appear in the graph
|
||||
continue
|
||||
month_days = pd.Timestamp(year=gp_m[0], month=gp_m[1], day=1).days_in_month
|
||||
_temp_df = _get_risk_analysis_data_with_report(
|
||||
_m_report_normal,
|
||||
# _m_report_long_short,
|
||||
pd.Timestamp(year=gp_m[0], month=gp_m[1], day=month_days),
|
||||
)
|
||||
_monthly_df = _monthly_df.append(_temp_df, sort=False)
|
||||
|
||||
return _monthly_df
|
||||
|
||||
|
||||
def _get_monthly_analysis_with_feature(
|
||||
monthly_df: pd.DataFrame, feature: str = "annual"
|
||||
) -> pd.DataFrame:
|
||||
"""
|
||||
|
||||
:param monthly_df:
|
||||
:param feature:
|
||||
:return:
|
||||
"""
|
||||
_monthly_df_gp = monthly_df.reset_index().groupby(["level_1"])
|
||||
|
||||
_name_df = _monthly_df_gp.get_group(feature).set_index(["level_0", "level_1"])
|
||||
_temp_df = _name_df.pivot_table(
|
||||
index="date", values=["risk"], columns=_name_df.index
|
||||
)
|
||||
_temp_df.columns = map(lambda x: "_".join(x[-1]), _temp_df.columns)
|
||||
_temp_df.index = _temp_df.index.strftime("%Y-%m")
|
||||
|
||||
return _temp_df
|
||||
|
||||
|
||||
def _get_risk_analysis_figure(analysis_df: pd.DataFrame) -> Iterable[py.Figure]:
|
||||
"""Get analysis graph figure
|
||||
|
||||
:param analysis_df:
|
||||
:return:
|
||||
"""
|
||||
if analysis_df is None:
|
||||
return []
|
||||
|
||||
_figure = SubplotsGraph(
|
||||
_get_all_risk_analysis(analysis_df), kind_map=dict(kind="BarGraph", kwargs={})
|
||||
).figure
|
||||
return (_figure,)
|
||||
|
||||
|
||||
def _get_monthly_risk_analysis_figure(report_normal_df: pd.DataFrame) -> Iterable[py.Figure]:
|
||||
"""Get analysis monthly graph figure
|
||||
|
||||
:param report_normal_df:
|
||||
:param report_long_short_df:
|
||||
:return:
|
||||
"""
|
||||
|
||||
# if report_normal_df is None and report_long_short_df is None:
|
||||
# return []
|
||||
if report_normal_df is None:
|
||||
return []
|
||||
|
||||
# if report_normal_df is None:
|
||||
# report_normal_df = pd.DataFrame(index=report_long_short_df.index)
|
||||
|
||||
# if report_long_short_df is None:
|
||||
# report_long_short_df = pd.DataFrame(index=report_normal_df.index)
|
||||
|
||||
_monthly_df = _get_monthly_risk_analysis_with_report(
|
||||
report_normal_df=report_normal_df,
|
||||
# report_long_short_df=report_long_short_df,
|
||||
)
|
||||
|
||||
for _feature in ["annual", "mdd", "sharpe", "std"]:
|
||||
_temp_df = _get_monthly_analysis_with_feature(_monthly_df, _feature)
|
||||
yield ScatterGraph(
|
||||
_temp_df,
|
||||
layout=dict(title=_feature, xaxis=dict(type="category", tickangle=45)),
|
||||
graph_kwargs={"mode": "lines+markers"},
|
||||
).figure
|
||||
|
||||
|
||||
def risk_analysis_graph(
|
||||
analysis_df: pd.DataFrame = None,
|
||||
report_normal_df: pd.DataFrame = None,
|
||||
report_long_short_df: pd.DataFrame = None,
|
||||
show_notebook: bool = True,
|
||||
) -> Iterable[py.Figure]:
|
||||
"""Generate analysis graph and monthly analysis
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from qlib.contrib.evaluate import risk_analysis, backtest, long_short_backtest
|
||||
from qlib.contrib.strategy import TopkDropoutStrategy
|
||||
from qlib.contrib.report import analysis_position
|
||||
|
||||
# backtest parameters
|
||||
bparas = {}
|
||||
bparas['limit_threshold'] = 0.095
|
||||
bparas['account'] = 1000000000
|
||||
|
||||
sparas = {}
|
||||
sparas['topk'] = 50
|
||||
sparas['n_drop'] = 230
|
||||
strategy = TopkDropoutStrategy(**sparas)
|
||||
|
||||
report_normal_df, positions = backtest(pred_df, strategy, **bparas)
|
||||
# long_short_map = long_short_backtest(pred_df)
|
||||
# report_long_short_df = pd.DataFrame(long_short_map)
|
||||
|
||||
analysis = dict()
|
||||
# analysis['pred_long'] = risk_analysis(report_long_short_df['long'])
|
||||
# analysis['pred_short'] = risk_analysis(report_long_short_df['short'])
|
||||
# analysis['pred_long_short'] = risk_analysis(report_long_short_df['long_short'])
|
||||
analysis['sub_bench'] = risk_analysis(report_normal_df['return'] - report_normal_df['bench'])
|
||||
analysis['sub_cost'] = risk_analysis(report_normal_df['return'] - report_normal_df['bench'] - report_normal_df['cost'])
|
||||
analysis_df = pd.concat(analysis)
|
||||
|
||||
analysis_position.risk_analysis_graph(analysis_df, report_normal_df)
|
||||
|
||||
|
||||
|
||||
:param analysis_df: analysis data, index is **pd.MultiIndex**; columns names is **[risk]**.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
risk
|
||||
sub_bench mean 0.000662
|
||||
std 0.004487
|
||||
annual 0.166720
|
||||
sharpe 2.340526
|
||||
mdd -0.080516
|
||||
sub_cost mean 0.000577
|
||||
std 0.004482
|
||||
annual 0.145392
|
||||
sharpe 2.043494
|
||||
mdd -0.083584
|
||||
|
||||
|
||||
:param report_normal_df: **df.index.name** must be **date**, df.columns must contain **return**, **turnover**, **cost**, **bench**
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
return cost bench turnover
|
||||
date
|
||||
2017-01-04 0.003421 0.000864 0.011693 0.576325
|
||||
2017-01-05 0.000508 0.000447 0.000721 0.227882
|
||||
2017-01-06 -0.003321 0.000212 -0.004322 0.102765
|
||||
2017-01-09 0.006753 0.000212 0.006874 0.105864
|
||||
2017-01-10 -0.000416 0.000440 -0.003350 0.208396
|
||||
|
||||
|
||||
:param report_long_short_df: **df.index.name** must be **date**, df.columns contain **long**, **short**, **long_short**
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
long short long_short
|
||||
date
|
||||
2017-01-04 -0.001360 0.001394 0.000034
|
||||
2017-01-05 0.002456 0.000058 0.002514
|
||||
2017-01-06 0.000120 0.002739 0.002859
|
||||
2017-01-09 0.001436 0.001838 0.003273
|
||||
2017-01-10 0.000824 -0.001944 -0.001120
|
||||
|
||||
|
||||
:param show_notebook: Whether to display graphics in a notebook, default **True**
|
||||
If True, show graph in notebook
|
||||
If False, return graph figure
|
||||
:return:
|
||||
"""
|
||||
_figure_list = list(_get_risk_analysis_figure(analysis_df)) + list(
|
||||
_get_monthly_risk_analysis_figure(
|
||||
report_normal_df,
|
||||
# report_long_short_df,
|
||||
)
|
||||
)
|
||||
if show_notebook:
|
||||
ScatterGraph.show_graph_in_notebook(_figure_list)
|
||||
else:
|
||||
return _figure_list
|
||||
72
qlib/contrib/report/analysis_position/score_ic.py
Normal file
72
qlib/contrib/report/analysis_position/score_ic.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from ..graph import ScatterGraph
|
||||
|
||||
|
||||
def _get_score_ic(pred_label: pd.DataFrame):
|
||||
"""
|
||||
|
||||
:param pred_label:
|
||||
:return:
|
||||
"""
|
||||
concat_data = pred_label.copy()
|
||||
concat_data.dropna(axis=0, how="any", inplace=True)
|
||||
_ic = concat_data.groupby(level="datetime").apply(
|
||||
lambda x: x["label"].corr(x["score"])
|
||||
)
|
||||
_rank_ic = concat_data.groupby(level="datetime").apply(
|
||||
lambda x: x["label"].corr(x["score"], method="spearman")
|
||||
)
|
||||
return pd.DataFrame({"ic": _ic, "rank_ic": _rank_ic})
|
||||
|
||||
|
||||
def score_ic_graph(
|
||||
pred_label: pd.DataFrame, show_notebook: bool = True
|
||||
) -> [list, tuple]:
|
||||
"""score IC
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from qlib.data import D
|
||||
from qlib.contrib.report import analysis_position
|
||||
pred_df_dates = pred_df.index.get_level_values(level='datetime')
|
||||
features_df = D.features(D.instruments('csi500'), ['Ref($close, -2)/Ref($close, -1)-1'], pred_df_dates.min(), pred_df_dates.max())
|
||||
features_df.columns = ['label']
|
||||
pred_label = pd.concat([features_df, pred], axis=1, sort=True).reindex(features_df.index)
|
||||
analysis_position.score_ic_graph(pred_label)
|
||||
|
||||
|
||||
:param pred_label: index is **pd.MultiIndex**, index name is **[instrument, datetime]**; columns names is **[score, label]**
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
instrument datetime score label
|
||||
SH600004 2017-12-11 -0.013502 -0.013502
|
||||
2017-12-12 -0.072367 -0.072367
|
||||
2017-12-13 -0.068605 -0.068605
|
||||
2017-12-14 0.012440 0.012440
|
||||
2017-12-15 -0.102778 -0.102778
|
||||
|
||||
|
||||
:param show_notebook: whether to display graphics in notebook, the default is **True**
|
||||
:return: if show_notebook is True, display in notebook; else return **plotly.graph_objs.Figure** list
|
||||
"""
|
||||
_ic_df = _get_score_ic(pred_label)
|
||||
# FIXME: support HIGH-FREQ
|
||||
_ic_df.index = _ic_df.index.strftime("%Y-%m-%d")
|
||||
_figure = ScatterGraph(
|
||||
_ic_df,
|
||||
layout=dict(title="Score IC", xaxis=dict(type="category", tickangle=45)),
|
||||
graph_kwargs={"mode": "lines+markers"},
|
||||
).figure
|
||||
if show_notebook:
|
||||
ScatterGraph.show_graph_in_notebook([_figure])
|
||||
else:
|
||||
return (_figure,)
|
||||
370
qlib/contrib/report/graph.py
Normal file
370
qlib/contrib/report/graph.py
Normal file
@@ -0,0 +1,370 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
import math
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
|
||||
import pandas as pd
|
||||
|
||||
import plotly.offline as py
|
||||
import plotly.graph_objs as go
|
||||
|
||||
from plotly.tools import make_subplots
|
||||
from plotly.figure_factory import create_distplot
|
||||
|
||||
from ...utils import get_module_by_module_path
|
||||
|
||||
|
||||
class BaseGraph(object):
|
||||
""""""
|
||||
|
||||
_name = None
|
||||
|
||||
def __init__(
|
||||
self, df: pd.DataFrame = None, layout: dict = None, graph_kwargs: dict = None, name_dict: dict = None, **kwargs
|
||||
):
|
||||
"""
|
||||
|
||||
:param df:
|
||||
:param layout:
|
||||
:param graph_kwargs:
|
||||
:param name_dict:
|
||||
:param kwargs:
|
||||
layout: dict
|
||||
go.Layout parameters
|
||||
graph_kwargs: dict
|
||||
Graph parameters, eg: go.Bar(**graph_kwargs)
|
||||
"""
|
||||
self._df = df
|
||||
|
||||
self._layout = dict() if layout is None else layout
|
||||
self._graph_kwargs = dict() if graph_kwargs is None else graph_kwargs
|
||||
self._name_dict = name_dict
|
||||
|
||||
self.data = None
|
||||
|
||||
self._init_parameters(**kwargs)
|
||||
self._init_data()
|
||||
|
||||
def _init_data(self):
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
if self._df.empty:
|
||||
raise ValueError("df is empty.")
|
||||
|
||||
self.data = self._get_data()
|
||||
|
||||
def _init_parameters(self, **kwargs):
|
||||
"""
|
||||
|
||||
:param kwargs
|
||||
"""
|
||||
|
||||
# Instantiate graphics parameters
|
||||
self._graph_type = self._name.lower().capitalize()
|
||||
|
||||
# Displayed column name
|
||||
if self._name_dict is None:
|
||||
self._name_dict = {_item: _item for _item in self._df.columns}
|
||||
|
||||
@staticmethod
|
||||
def get_instance_with_graph_parameters(graph_type: str = None, **kwargs):
|
||||
"""
|
||||
|
||||
:param graph_type:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
_graph_module = importlib.import_module("plotly.graph_objs")
|
||||
_graph_class = getattr(_graph_module, graph_type)
|
||||
except AttributeError:
|
||||
_graph_module = importlib.import_module("qlib.contrib.report.graph")
|
||||
_graph_class = getattr(_graph_module, graph_type)
|
||||
return _graph_class(**kwargs)
|
||||
|
||||
@staticmethod
|
||||
def show_graph_in_notebook(figure_list: Iterable[go.Figure] = None):
|
||||
"""
|
||||
|
||||
:param figure_list:
|
||||
:return:
|
||||
"""
|
||||
py.init_notebook_mode()
|
||||
for _fig in figure_list:
|
||||
py.iplot(_fig)
|
||||
|
||||
def _get_layout(self) -> go.Layout:
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
return go.Layout(**self._layout)
|
||||
|
||||
def _get_data(self) -> list:
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
|
||||
_data = [
|
||||
self.get_instance_with_graph_parameters(
|
||||
graph_type=self._graph_type, x=self._df.index, y=self._df[_col], name=_name, **self._graph_kwargs
|
||||
)
|
||||
for _col, _name in self._name_dict.items()
|
||||
]
|
||||
return _data
|
||||
|
||||
@property
|
||||
def figure(self) -> go.Figure:
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
return go.Figure(data=self.data, layout=self._get_layout())
|
||||
|
||||
|
||||
class ScatterGraph(BaseGraph):
|
||||
_name = "scatter"
|
||||
|
||||
|
||||
class BarGraph(BaseGraph):
|
||||
_name = "bar"
|
||||
|
||||
|
||||
class DistplotGraph(BaseGraph):
|
||||
_name = "distplot"
|
||||
|
||||
def _get_data(self):
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
_t_df = self._df.dropna()
|
||||
_data_list = [_t_df[_col] for _col in self._name_dict]
|
||||
_label_list = [_name for _name in self._name_dict.values()]
|
||||
_fig = create_distplot(_data_list, _label_list, show_rug=False, **self._graph_kwargs)
|
||||
|
||||
return _fig["data"]
|
||||
|
||||
|
||||
class HeatmapGraph(BaseGraph):
|
||||
_name = "heatmap"
|
||||
|
||||
def _get_data(self):
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
_data = [
|
||||
self.get_instance_with_graph_parameters(
|
||||
graph_type=self._graph_type,
|
||||
x=self._df.columns,
|
||||
y=self._df.index,
|
||||
z=self._df.values.tolist(),
|
||||
**self._graph_kwargs
|
||||
)
|
||||
]
|
||||
return _data
|
||||
|
||||
|
||||
class HistogramGraph(BaseGraph):
|
||||
_name = "histogram"
|
||||
|
||||
def _get_data(self):
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
_data = [
|
||||
self.get_instance_with_graph_parameters(
|
||||
graph_type=self._graph_type, x=self._df[_col], name=_name, **self._graph_kwargs
|
||||
)
|
||||
for _col, _name in self._name_dict.items()
|
||||
]
|
||||
return _data
|
||||
|
||||
|
||||
class SubplotsGraph(object):
|
||||
"""Create subplots same as df.plot(subplots=True)
|
||||
|
||||
Simple package for `plotly.tools.subplots`
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
df: pd.DataFrame = None,
|
||||
kind_map: dict = None,
|
||||
layout: dict = None,
|
||||
sub_graph_layout: dict = None,
|
||||
sub_graph_data: list = None,
|
||||
subplots_kwargs: dict = None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
|
||||
:param df: pd.DataFrame
|
||||
|
||||
:param kind_map: dict, subplots graph kind and kwargs
|
||||
eg: dict(kind='ScatterGraph', kwargs=dict())
|
||||
|
||||
:param layout: `go.Layout` parameters
|
||||
|
||||
:param sub_graph_layout: Layout of each graphic, similar to 'layout'
|
||||
|
||||
:param sub_graph_data: Instantiation parameters for each sub-graphic
|
||||
eg: [(column_name, instance_parameters), ]
|
||||
|
||||
column_name: str or go.Figure
|
||||
|
||||
Instance_parameters:
|
||||
|
||||
- row: int, the row where the graph is located
|
||||
|
||||
- col: int, the col where the graph is located
|
||||
|
||||
- name: str, show name, default column_name in 'df'
|
||||
|
||||
- kind: str, graph kind, default `kind` param, eg: bar, scatter, ...
|
||||
|
||||
- graph_kwargs: dict, graph kwargs, default {}, used in `go.Bar(**graph_kwargs)`
|
||||
|
||||
:param subplots_kwargs: `plotly.tools.make_subplots` original parameters
|
||||
|
||||
- shared_xaxes: bool, default False
|
||||
|
||||
- shared_yaxes: bool, default False
|
||||
|
||||
- vertical_spacing: float, default 0.3 / rows
|
||||
|
||||
- subplot_titles: list, default []
|
||||
If `sub_graph_data` is None, will generate 'subplot_titles' according to `df.columns`,
|
||||
this field will be discarded
|
||||
|
||||
|
||||
- specs: list, see `make_subplots` docs
|
||||
|
||||
- rows: int, Number of rows in the subplot grid, default 1
|
||||
If `sub_graph_data` is None, will generate 'rows' according to `df`, this field will be discarded
|
||||
|
||||
- cols: int, Number of cols in the subplot grid, default 1
|
||||
If `sub_graph_data` is None, will generate 'cols' according to `df`, this field will be discarded
|
||||
|
||||
|
||||
:param kwargs:
|
||||
|
||||
"""
|
||||
|
||||
self._df = df
|
||||
self._layout = layout
|
||||
self._sub_graph_layout = sub_graph_layout
|
||||
|
||||
self._kind_map = kind_map
|
||||
if self._kind_map is None:
|
||||
self._kind_map = dict(kind="ScatterGraph", kwargs=dict())
|
||||
|
||||
self._subplots_kwargs = subplots_kwargs
|
||||
if self._subplots_kwargs is None:
|
||||
self._init_subplots_kwargs()
|
||||
|
||||
self.__cols = self._subplots_kwargs.get("cols", 2)
|
||||
self.__rows = self._subplots_kwargs.get("rows", math.ceil(len(self._df.columns) / self.__cols))
|
||||
|
||||
self._sub_graph_data = sub_graph_data
|
||||
if self._sub_graph_data is None:
|
||||
self._init_sub_graph_data()
|
||||
|
||||
self._init_figure()
|
||||
|
||||
def _init_sub_graph_data(self):
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
self._sub_graph_data = list()
|
||||
self._subplot_titles = list()
|
||||
|
||||
for i, column_name in enumerate(self._df.columns):
|
||||
row = math.ceil((i + 1) / self.__cols)
|
||||
_temp = (i + 1) % self.__cols
|
||||
col = _temp if _temp else self.__cols
|
||||
res_name = column_name.replace("_", " ")
|
||||
_temp_row_data = (
|
||||
column_name,
|
||||
dict(
|
||||
row=row,
|
||||
col=col,
|
||||
name=res_name,
|
||||
kind=self._kind_map["kind"],
|
||||
graph_kwargs=self._kind_map["kwargs"],
|
||||
),
|
||||
)
|
||||
self._sub_graph_data.append(_temp_row_data)
|
||||
self._subplot_titles.append(res_name)
|
||||
|
||||
def _init_subplots_kwargs(self):
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
# Default cols, rows
|
||||
_cols = 2
|
||||
_rows = math.ceil(len(self._df.columns) / 2)
|
||||
self._subplots_kwargs = dict()
|
||||
self._subplots_kwargs["rows"] = _rows
|
||||
self._subplots_kwargs["cols"] = _cols
|
||||
self._subplots_kwargs["shared_xaxes"] = False
|
||||
self._subplots_kwargs["shared_yaxes"] = False
|
||||
self._subplots_kwargs["vertical_spacing"] = 0.3 / _rows
|
||||
self._subplots_kwargs["print_grid"] = False
|
||||
self._subplots_kwargs["subplot_titles"] = self._df.columns.tolist()
|
||||
|
||||
def _init_figure(self):
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
self._figure = make_subplots(**self._subplots_kwargs)
|
||||
|
||||
for column_name, column_map in self._sub_graph_data:
|
||||
if isinstance(column_name, go.Figure):
|
||||
_graph_obj = column_name
|
||||
elif isinstance(column_name, str):
|
||||
temp_name = column_map.get("name", column_name.replace("_", " "))
|
||||
kind = column_map.get("kind", self._kind_map.get("kind", "ScatterGraph"))
|
||||
_graph_kwargs = column_map.get("graph_kwargs", self._kind_map.get("kwargs", {}))
|
||||
_graph_obj = BaseGraph.get_instance_with_graph_parameters(
|
||||
kind,
|
||||
**dict(
|
||||
df=self._df.loc[:, [column_name]],
|
||||
name_dict={column_name: temp_name},
|
||||
graph_kwargs=_graph_kwargs,
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise TypeError()
|
||||
|
||||
row = column_map["row"]
|
||||
col = column_map["col"]
|
||||
|
||||
_graph_data = getattr(_graph_obj, "data")
|
||||
# for _item in _graph_data:
|
||||
# _item.pop('xaxis', None)
|
||||
# _item.pop('yaxis', None)
|
||||
|
||||
for _g_obj in _graph_data:
|
||||
self._figure.append_trace(_g_obj, row=row, col=col)
|
||||
|
||||
if self._sub_graph_layout is not None:
|
||||
for k, v in self._sub_graph_layout.items():
|
||||
self._figure["layout"][k].update(v)
|
||||
|
||||
self._figure["layout"].update(self._layout)
|
||||
|
||||
@property
|
||||
def figure(self):
|
||||
return self._figure
|
||||
Reference in New Issue
Block a user