Skip to content

Commit

Permalink
Merge pull request #771 from ricequant/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
Cuizi7 committed Mar 2, 2023
2 parents 45c367b + d5afadc commit 80f14e1
Show file tree
Hide file tree
Showing 27 changed files with 147 additions and 52 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Expand Up @@ -2,6 +2,13 @@
CHANGELOG
==================

4.15.0
==================
- 修复python3.10在Ubuntu和Centos下更新数据异常
- 期货回测支持使用结算价进行结算
- 修正报告中跟踪误差,使用年化跟踪误差
- 修改超额收益相关指标的计算,具体公式可参考rqrisk

4.14.1
==================
- ricequant报告图模版调整超额收益曲线,公式改为每日的超额收益率的累计
Expand Down
12 changes: 11 additions & 1 deletion rqalpha/mod/rqalpha_mod_sys_accounts/__init__.py
Expand Up @@ -32,7 +32,9 @@
# 融资利率/年
"financing_rate": 0.00,
# 是否开启融资可买入股票的限制
"financing_stocks_restriction_enabled": False
"financing_stocks_restriction_enabled": False,
# 逐日盯市结算价: settlement/close
"futures_settlement_price_type": "close",
}


Expand Down Expand Up @@ -79,3 +81,11 @@ def load_mod():
help="[sys_accounts] enable stock shorting"
)
)

cli.commands['run'].params.append(
click.Option(
('--futures-settlement-price-type', cli_prefix + 'futures_settlement_price_type'),
default="close",
help="[sys_accounts] future settlement price"
)
)
4 changes: 4 additions & 0 deletions rqalpha/mod/rqalpha_mod_sys_accounts/mod.py
Expand Up @@ -50,6 +50,10 @@ def start_up(self, env, mod_config):
elif mod_config.financing_rate < 0:
raise ValueError("sys_accounts financing_rate must >= 0")

futures_settlement_price_types = ["settlement", "close"]
if mod_config.futures_settlement_price_type not in futures_settlement_price_types:
raise ValueError("sys_accounts futures_settlement_price_type in {}".format(futures_settlement_price_types))

if DEFAULT_ACCOUNT_TYPE.FUTURE in env.config.base.accounts:
# 注入期货API
# noinspection PyUnresolvedReferences
Expand Down
23 changes: 21 additions & 2 deletions rqalpha/mod/rqalpha_mod_sys_accounts/position_model.py
Expand Up @@ -19,13 +19,12 @@

from decimal import Decimal

from rqalpha.utils.functools import lru_cache
from rqalpha.model.trade import Trade
from rqalpha.const import POSITION_DIRECTION, SIDE, POSITION_EFFECT, DEFAULT_ACCOUNT_TYPE, INSTRUMENT_TYPE
from rqalpha.environment import Environment
from rqalpha.portfolio.position import Position, PositionProxy
from rqalpha.data.data_proxy import DataProxy
from rqalpha.utils import INST_TYPE_IN_STOCK_ACCOUNT
from rqalpha.utils import INST_TYPE_IN_STOCK_ACCOUNT, is_valid_price
from rqalpha.utils.logger import user_system_log
from rqalpha.utils.class_helper import deprecated_property, cached_property
from rqalpha.utils.i18n import gettext as _
Expand Down Expand Up @@ -281,6 +280,15 @@ def apply_trade(self, trade):
trade.last_price - self._avg_price
) * trade.last_quantity * self.contract_multiplier * self._direction_factor

@property
def prev_close(self):
if not is_valid_price(self._prev_close):
if self._env.config.mod.sys_accounts.futures_settlement_price_type == "settlement":
self._prev_close = self._env.data_proxy.get_prev_settlement(self._order_book_id, self._env.trading_dt)
else:
self._prev_close = super().prev_close
return self._prev_close

def settlement(self, trading_date):
# type: (date) -> float
super(FuturePosition, self).settlement(trading_date)
Expand All @@ -295,9 +303,20 @@ def settlement(self, trading_date):
order_book_id=self._order_book_id
))
self._quantity = self._old_quantity = 0

if self._env.config.mod.sys_accounts.futures_settlement_price_type == "settlement":
# 逐日盯市按照结算价结算
self._last_price = self._env.data_proxy.get_settle_price(self._order_book_id, self._env.trading_dt)

self._avg_price = self.last_price
return delta_cash

def before_trading(self, trading_date):
# type: (date) -> float
delta = super().before_trading(trading_date)
self._last_price = None
return delta


class StockPositionProxy(PositionProxy):
__repr_properties__ = (
Expand Down
15 changes: 6 additions & 9 deletions rqalpha/mod/rqalpha_mod_sys_analyser/mod.py
Expand Up @@ -327,14 +327,14 @@ def tear_down(self, code, exception=None):
'sortino': risk.sortino,
'volatility': risk.annual_volatility,
'excess_volatility': risk.excess_annual_volatility,
'excess_annual_volatility': risk.excess_annual_volatility,
'max_drawdown': risk.max_drawdown,
'excess_max_drawdown': risk.excess_max_drawdown,
'excess_returns': risk.excess_return_rate,
'excess_annual_returns': risk.excess_annual_return,
'excess_max_drawdown': risk.geometric_excess_drawdown,
'excess_returns': risk.geometric_excess_return,
'excess_annual_returns': risk.geometric_excess_annual_return,
'var': risk.var,
"win_rate": risk.win_rate,
"excess_win_rate": risk.excess_win_rate,
"excess_cum_returns": risk.arithmetic_excess_return,
})

# 盈亏比
Expand Down Expand Up @@ -362,9 +362,6 @@ def tear_down(self, code, exception=None):
benchmark_annualized_returns = (benchmark_total_returns + 1) ** (DAYS_CNT.TRADING_DAYS_A_YEAR / date_count) - 1
summary['benchmark_annualized_returns'] = benchmark_annualized_returns

# 新增一个超额累计收益
summary['excess_cum_returns'] = summary["total_returns"] - summary["benchmark_total_returns"]

trades = pd.DataFrame(self._trades)
if 'datetime' in trades.columns:
trades = trades.set_index(pd.DatetimeIndex(trades['datetime']))
Expand Down Expand Up @@ -417,7 +414,7 @@ def tear_down(self, code, exception=None):
monthly_b_returns = (monthly_b_nav / monthly_b_nav.shift(1).fillna(1)).fillna(0) - 1
result_dict['benchmark_portfolio'] = benchmark_portfolios
# 超额收益最长回撤持续期
ex_returns = total_portfolios.unit_net_value - benchmark_portfolios.unit_net_value
ex_returns = total_portfolios.unit_net_value / benchmark_portfolios.unit_net_value - 1
max_ddd = _max_ddd(ex_returns + 1, total_portfolios.index)
result_dict["summary"]["excess_max_drawdown_duration"] = max_ddd
result_dict["summary"]["excess_max_drawdown_duration_start_date"] = str(max_ddd.start_date)
Expand All @@ -435,7 +432,7 @@ def tear_down(self, code, exception=None):
"weekly_sharpe": weekly_risk.sharpe,
"weekly_sortino": weekly_risk.sortino,
"weekly_information_ratio": weekly_risk.information_ratio,
"weekly_tracking_error": weekly_risk.tracking_error,
"weekly_tracking_error": weekly_risk.annual_tracking_error,
"weekly_max_drawdown": weekly_risk.max_drawdown,
"weekly_win_rate": weekly_risk.win_rate,
"weekly_volatility": weekly_risk.annual_volatility,
Expand Down
14 changes: 4 additions & 10 deletions rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py
Expand Up @@ -83,6 +83,10 @@ def __init__(self, p_nav, b_nav):
WEEKLY_INDICATORS = []
EXCESS_INDICATORS = []

@property
def geometric_excess_returns(self):
return self.p_nav / self.b_nav - 1


class DefaultPlot(PlotTemplate):
""" 基础 """
Expand Down Expand Up @@ -137,10 +141,6 @@ class DefaultPlot(PlotTemplate):
IndicatorInfo("weekly_excess_ulcer_performance_index", _("WeeklyExcessUlcerPerformanceIndex"), BLACK, "{0:.4}", 11, 1.4),
]]

@property
def excess_returns(self):
return self.p_nav - self.b_nav


class RiceQuant(PlotTemplate):
# 指标的宽高
Expand Down Expand Up @@ -184,12 +184,6 @@ class RiceQuant(PlotTemplate):

WEEKLY_INDICATORS = []

@property
def excess_returns(self):
p_return = (self.p_nav / self.p_nav.shift(1).fillna(1)).fillna(0) - 1
b_return = (self.b_nav / self.b_nav.shift(1).fillna(1)).fillna(0) - 1
return ((p_return - b_return) + 1).cumprod() - 1


PLOT_TEMPLATE: dict = {
"default": DefaultPlot,
Expand Down
6 changes: 3 additions & 3 deletions rqalpha/mod/rqalpha_mod_sys_analyser/plot/plot.py
Expand Up @@ -99,8 +99,8 @@ def _plot_spots_on_returns(self, ax, positions: Sequence[int], info: SpotInfo):
def plot(self, ax: Axes):
ax.get_xaxis().set_minor_locator(ticker.AutoMinorLocator())
ax.get_yaxis().set_minor_locator(ticker.AutoMinorLocator())
ax.grid(b=True, which='minor', linewidth=.2)
ax.grid(b=True, which='major', linewidth=1)
ax.grid(visible=True, which='minor', linewidth=.2)
ax.grid(visible=True, which='major', linewidth=1)
ax.patch.set_alpha(0.6)

# plot lines
Expand Down Expand Up @@ -176,7 +176,7 @@ def plot_result(
if "benchmark_portfolio" in result_dict:
benchmark_portfolio = result_dict["benchmark_portfolio"]
plot_template = plot_template_cls(portfolio.unit_net_value, benchmark_portfolio.unit_net_value)
ex_returns = plot_template.excess_returns
ex_returns = plot_template.geometric_excess_returns
ex_max_dd_ddd = "MaxDD {}\nMaxDDD {}".format(
_max_dd(ex_returns + 1, portfolio.index).repr, _max_ddd(ex_returns + 1, portfolio.index).repr
)
Expand Down
Expand Up @@ -81,7 +81,7 @@ class SummaryTemplate(ExcelTemplate):
"概览": SingleCellSchema,
"年度指标": VerticalSeriesSchema,
"月度收益": VerticalSeriesSchema,
"月度主动收益": VerticalSeriesSchema
"月度超额收益(几何)": VerticalSeriesSchema
}


Expand Down
29 changes: 18 additions & 11 deletions rqalpha/mod/rqalpha_mod_sys_analyser/report/report.py
Expand Up @@ -37,8 +37,8 @@ def _yearly_indicators(
p_nav: Series, p_returns: Series, b_nav: Optional[Series], b_returns: Optional[Series], risk_free_rates: Dict
):
data = {field: [] for field in [
"year", "returns", "benchmark_returns", "active_returns", "active_max_drawdown",
"active_max_drawdown_days", "sharpe_ratio", "information_ratio", "tracking_error",
"year", "returns", "benchmark_returns", "geometric_excess_return", "geometric_excess_drawdown",
"geometric_excess_drawdown_days", "sharpe_ratio", "information_ratio", "annual_tracking_error",
"weekly_excess_win_rate", "monthly_excess_win_rate"
]}

Expand All @@ -61,18 +61,18 @@ def _yearly_indicators(
weekly_excess_win_rate = numpy.nan
monthly_excess_win_rate = numpy.nan
b_year_returns = Series(index=p_year_returns.index)
excess_returns = ((p_year_returns - b_year_returns) + 1).cumprod()
max_dd = _max_dd(excess_returns.values, excess_returns.index)
excess_nav = (p_year_returns + 1).cumprod() / (b_year_returns + 1).cumprod()
max_dd = _max_dd(excess_nav.values, excess_nav.index)
risk = Risk(p_year_returns, b_year_returns, risk_free_rates[year])
data["year"].append(year)
data["returns"].append(risk.return_rate)
data["benchmark_returns"].append(risk.benchmark_return)
data["active_returns"].append(risk.excess_return_rate)
data["active_max_drawdown"].append(risk.excess_max_drawdown)
data["active_max_drawdown_days"].append((max_dd.end_date - max_dd.start_date).days)
data["geometric_excess_return"].append(risk.geometric_excess_return)
data["geometric_excess_drawdown"].append(risk.geometric_excess_drawdown)
data["geometric_excess_drawdown_days"].append((max_dd.end_date - max_dd.start_date).days)
data["sharpe_ratio"].append(risk.sharpe)
data["information_ratio"].append(risk.information_ratio)
data["tracking_error"].append(risk.tracking_error)
data["annual_tracking_error"].append(risk.annual_tracking_error)
data["weekly_excess_win_rate"].append(weekly_excess_win_rate)
data["monthly_excess_win_rate"].append(monthly_excess_win_rate)
return data
Expand All @@ -87,10 +87,17 @@ def _monthly_returns(p_returns: Series):
return ChainMap({str(c): data[c] for c in data.columns}, {"year": data.index})


def _monthly_active_returns(p_returns: Series, b_returns: Optional[Series]):
def _monthly_geometric_excess_returns(p_returns: Series, b_returns: Optional[Series]):
if b_returns is None:
return {}
return _monthly_returns(p_returns - b_returns)
data = DataFrame(index=p_returns.index.year.unique(), columns=list(range(1, 13)))
for year, p_year_returns in p_returns.groupby(p_returns.index.year):
for month, p_month_returns in p_year_returns.groupby(p_year_returns.index.month):
b_month_returns = b_returns.loc[p_month_returns.index]
data.loc[year, month] = (p_month_returns + 1).prod() / (b_month_returns + 1).prod() - 1
b_year_returns = b_returns.loc[p_year_returns.index]
data.loc[year, "cum"] = (p_year_returns + 1).prod() / (b_year_returns + 1).prod() - 1
return ChainMap({str(c): data[c] for c in data.columns}, {"year": data.index})


def generate_report(result_dict, output_path):
Expand All @@ -115,7 +122,7 @@ def generate_report(result_dict, output_path):
"概览": summary,
"年度指标": _yearly_indicators(p_nav, p_returns, b_nav, b_returns, result_dict["yearly_risk_free_rates"]),
"月度收益": _monthly_returns(p_returns),
"月度主动收益": _monthly_active_returns(p_returns, b_returns)
"月度超额收益(几何)": _monthly_geometric_excess_returns(p_returns, b_returns)
}, output_path)

for name in ["portfolio", "stock_account", "future_account",
Expand Down
Binary file modified rqalpha/mod/rqalpha_mod_sys_analyser/report/templates/summary.xlsx
Binary file not shown.
18 changes: 10 additions & 8 deletions rqalpha/utils/concurrent.py
Expand Up @@ -50,14 +50,16 @@ def __init__(self, max_workers=None, initializer=None, initargs=()):
def _adjust_process_count(self):
# noinspection PyUnresolvedReferences
for _ in range(len(self._processes), self._max_workers):
# noinspection PyUnresolvedReferences
p = multiprocessing.Process(
target=_process_worker,
args=(self._call_queue, self._result_queue, self._progress_queue, self._initializer, self._initargs)
)
p.start()
# noinspection PyUnresolvedReferences
self._processes[p.pid] = p
self._spawn_process()

def _spawn_process(self):
p = multiprocessing.Process(
target=_process_worker,
args=(self._call_queue, self._result_queue, self._progress_queue, self._initializer, self._initargs)
)
p.start()
# noinspection PyUnresolvedReferences
self._processes[p.pid] = p

def submit(self, fn, *args, **kwargs):
if isinstance(fn, ProgressedTask):
Expand Down
Binary file modified rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.mo
Binary file not shown.
8 changes: 4 additions & 4 deletions rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.po
Expand Up @@ -519,7 +519,7 @@ msgstr "周度累计回撤夏普率"

#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:87
msgid "ExcessCumReturns"
msgstr "超额累计收益"
msgstr "超额收益率(算术)"

#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:88
msgid "WinRate"
Expand Down Expand Up @@ -575,11 +575,11 @@ msgstr "月度波动率"

#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:105
msgid "ExcessReturns"
msgstr "超额收益率"
msgstr "超额收益率(几何)"

#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:106
msgid "ExcessAnnual"
msgstr "年化超额收益率"
msgstr "年化超额收益率(几何)"

#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:107
msgid "ExcessSharpe"
Expand All @@ -591,7 +591,7 @@ msgstr "超额收益波动率"

#: rqalpha/mod/rqalpha_mod_sys_analyser/plot/consts.py:109
msgid "ExcessMaxDD"
msgstr "超额收益最大回撤"
msgstr "超额收益最大回撤(几何)"

msgid "ExcessWinRate"
msgstr "超额胜率"
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Expand Up @@ -5,7 +5,7 @@

[metadata]
name = rqalpha
version = 4.14.1
version = 4.15.0

[versioneer]
VCS = git
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Expand Up @@ -27,9 +27,9 @@
'simplejson',
'PyYAML',
'tabulate',
'rqrisk >=1.0.5',
'rqrisk >=1.0.6',
'h5py',
'matplotlib >=2.2.0',
'matplotlib >=3.1.0',
"openpyxl",
"methodtools"
]
Expand Down

0 comments on commit 80f14e1

Please sign in to comment.