Repository: ricequant/rqalpha (language: python)

Issue:
release 4.6.0



Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index a49005445..31e29a4e5 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -2,6 +2,58 @@
 CHANGELOG
 ==================
 
+4.5.3
+==================
+- 策略运行结束后绘制的收益图中加入超额收益曲线、周度收益曲线，加入部分超额收益和周度风险指标
+- 策略运行结束后输出的结果中加入部分超额收益和周度风险指标
+
+
+4.5.2
+==================
+- 放宽 :code:`BaseDataSource.get_open_auction_bar` 方法对于 bundle 数据字段的要求
+
+
+4.5.1
+==================
+
+- :code:`Instrument` 对象增加 :code:`trading_code` 字段，意为该标的在交易所的代码
+- 修复 :code:`get_pit_financials_ex` 的异常行为
+- :code:`get_positions` 接口不再返回数量为 0 的持仓对象
+- 修复了在分红未到账时平仓会导致分红金额始终不到账的问题
+- 修复了 :code:`get_open_orders` 取不到集合竞价阶段挂单的问题及其导致的冻结资金异常问题
+- 修复了个别情况下持仓盈亏和交易盈亏计算错误的问题
+
+
+4.5.0
+==================
+
+- 新增逐档撮合，该撮合方式会根据 tick 行情中的多档挂单信息逐步撮合订单。可在 tick 回测中设置 matching_type 为 :code:`counterparty_offer` 以启用。
+- 修复挂单进入终结状态时解冻资金金额异常的问题
+
+
+4.4.2
+==================
+
+- **RQAlpha 从该版本开始不再提供对 Python3.5 的支持**
+- :code:`get_financials` 被标记为废弃，请使用 :code:`get_pit_financials_ex`
+- 修复了在未设置基准的情况下，部分不应产生结果的风险指标出现异常计算结果的问题
+- 修复因浮点数精度问题导致的股票拆分数量错误
+
+
+4.4.1
+==================
+
+- 修复了调取 :code:`history_bars` 获取到错误的复权价的问题
+
+
+4.4.0
+==================
+
+- :code:`DataSource` interface 增加了 :code:`get_open_auction_bar` 接口。通过实现该接口，模拟交易可提供在集合竞价阶段获取 bar 的功能。
+- 修复了 Windows 下导出 csv 报告格式异常的问题
+- 修复了其他 bug
+
+
 4.3.3
 ==================
 
diff --git a/docs/source/development/collecting_logs.rst b/docs/source/development/collecting_logs.rst
index a3a25e8d3..7ff3a4374 100644
--- a/docs/source/development/collecting_logs.rst
+++ b/docs/source/development/collecting_logs.rst
@@ -6,6 +6,8 @@
 
 RQAlpha 采用 `logbook`_ 作为默认的日志模块，开发者可以通过在 mod 中为 logger 添加 handler 实现自定义的日志收集。
 
+以下是一个简单的 demo。
+
 .. _`logbook`: https://logbook.readthedocs.io/en/stable/
 
 
@@ -14,8 +16,6 @@ Mod 示例
 
 首先要做的是实现 `handler`_ 对象，此处实现的 handler 对象接受 :code:`send_log_handler` 函数作为参数，该函数会在打印日志的时候被调用。
 
-formatter 使用了 :code:`rqalpha.utils.logger.user_std_handler_log_formatter`，该 formatter 在输出策略日志的时候会选用策略运行的时间而非物理时间作为日志的时间戳。
-
 .. _`handler`: https://logbook.readthedocs.io/en/stable/quickstart.html#handlers
 
 .. code-block:: python
@@ -24,7 +24,6 @@ formatter 使用了 :code:`rqalpha.utils.logger.user_std_handler_log_formatter`
     from logbook.base import NOTSET
 
     from rqalpha.environment import Environment
-    from rqalpha.utils.logger import user_std_handler_log_formatter
 
 
     class LogHandler(Handler, StringFormatterHandlerMixin):
@@ -32,7 +31,6 @@ formatter 使用了 :code:`rqalpha.utils.logger.user_std_handler_log_formatter`
             Handler.__init__(self, level, filter, bubble)
             StringFormatterHandlerMixin.__init__(self, format_string)
             self.send_log_handler = send_log_handler
-            self.formatter = user_std_handler_log_formatter
 
         def _write(self, level_name, item):
             dt = Environment.get_instance().calendar_dt
diff --git a/docs/source/intro/run_algorithm.rst b/docs/source/intro/run_algorithm.rst
index cb4e97c28..a59d69da0 100644
--- a/docs/source/intro/run_algorithm.rst
+++ b/docs/source/intro/run_algorithm.rst
@@ -70,8 +70,8 @@ N/A           `- -` no-plot                   [sys_analyser]在回测结束后
 N/A           `- -` plot-save                 [sys_analyser]将plot的收益图以指定文件路径保存
 N/A           `- -` progress                  [sys_progress]开启命令行显示回测进度条
 N/A           `- -` no-progress               [sys_progress]关闭命令行查看回测进度
-N/A           `- -` short-stock               [sys_risk]允许股票卖空
-N/A           `- -` no-short-stock            [sys_risk]不允许股票卖空
+N/A           `- -` short-stock               [sys_accounts]允许股票卖空
+N/A           `- -` no-short-stock            [sys_accounts]不允许股票卖空
 N/A           `- -` signal                    [sys_simulation]开启信号模式，不进行撮合，直接成交
 -sp           `- -` slippage                  [sys_simulation]设置滑点
 -cm           `- -` commission-multiplier     [sys_simulation]设置手续费乘数，默认为1
diff --git a/messages.pot b/messages.pot
index 853a5edcc..963c1116a 100644
--- a/messages.pot
+++ b/messages.pot
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2021-05-14 09:37+0800\n"
+"POT-Creation-Date: 2021-09-08 11:05+0800\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -76,9 +76,9 @@ msgstr ""
 
 #: rqalpha/apis/api_base.py:159
 #: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:66
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:85
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:132
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:307
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:90
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:137
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:312
 msgid "Order Creation Failed: [{order_book_id}] No market data"
 msgstr ""
 
@@ -219,7 +219,7 @@ msgstr ""
 msgid "mod tear_down [END]   {}"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_accounts/position_model.py:290
+#: rqalpha/mod/rqalpha_mod_sys_accounts/position_model.py:294
 msgid "{order_book_id} is expired, close all positions by system"
 msgstr ""
 
@@ -238,13 +238,13 @@ msgid ""
 msgstr ""
 
 #: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:49
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:98
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:153
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:103
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:158
 msgid "Order Creation Failed: 0 order quantity, order_book_id={order_book_id}"
 msgstr ""
 
 #: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:54
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:81
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:86
 msgid "Limit order price should be positive"
 msgstr ""
 
@@ -266,102 +266,176 @@ msgid ""
 "[{new_orders_repr}]"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:108
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:67
+msgid ""
+"order_book_id: {order_book_id} needs stock account, please set and try "
+"again!"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:113
 msgid "insufficient cash, use all remaining cash({}) to create order"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:292
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:297
 msgid "total percent should be lower than 1, current: {}"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:301
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:306
 msgid "target percent of {} should between 0 and 1, current: {}"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:644
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:649
 msgid "in get_dividend, start_date {} is later than the previous test day {}"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:99
+#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:100
 msgid ""
 "config 'base.benchmark' is deprecated, use 'mod.sys_analyser.benchmark' "
 "instead"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:181
+#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:182
 msgid "invalid init benchmark {}, should be in format 'order_book_id:weight'"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:186
+#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:187
 msgid "invalid weight for instrument {order_book_id}: {weight}"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:128
-msgid "Total Returns"
-msgstr ""
-
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:129
-msgid "Annual Returns"
-msgstr ""
-
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:130
-msgid "Benchmark Returns"
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:71
+msgid "TotalReturns"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:132
-msgid "Benchmark Annual"
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:72
+msgid "AnnualReturns"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:135
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:73
 msgid "Alpha"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:136
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:74
 msgid "Beta"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:137
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:75
 msgid "Sharpe"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:138
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:76
 msgid "Sortino"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:139
-msgid "Information Ratio"
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:77
+msgid "InformationRatio"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:141
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:79
+msgid "BenchmarkReturns"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:80
+msgid "BenchmarkAnnual"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:81
 msgid "Volatility"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:142
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:172
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:82
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:199
 msgid "MaxDrawdown"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:143
-msgid "Tracking Error"
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:83
+msgid "TrackingError"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:144
-msgid "Downside Risk"
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:84
+msgid "DownsideRisk"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:153
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:85
 msgid "MaxDD/MaxDDD"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:166
-msgid "strategy"
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:87
+msgid "WeeklyAlpha"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:88
+msgid "WeeklyBeta"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:89
+msgid "WeeklySharpe"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:90
+msgid "WeeklySortino"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:91
+msgid "WeeklyInfoRatio"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:92
+msgid "WeeklyTrackingError"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:93
+msgid "WeeklyMaxDrawdown"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:97
+msgid "ExcessReturns"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:168
-msgid "benchmark"
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:98
+msgid "ExcessAnnual"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:175
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:99
+msgid "ExcessSharpe"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:100
+msgid "ExcessVolatility"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:101
+msgid "ExcessAnnualVolatility"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:102
+msgid "ExcessMaxDD"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:103
+msgid "ExcessMaxDD/ExcessMaxDDD"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:189
+msgid "Strategy"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:191
+msgid "Benchmark"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:193
+msgid "Excess"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:194
+msgid "Weekly"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:196
+msgid "BenchmarkWeekly"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:200
 msgid "MaxDDD"
 msgstr ""
 
@@ -401,64 +475,70 @@ msgid ""
 "trade: [{}...]"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:115
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:116
 #: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:66
 msgid ""
 "Order Cancelled: current security [{order_book_id}] can not be traded in "
 "listed date [{listed_date}]"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:122
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:123
 #: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:72
 msgid "Order Cancelled: current bar [{order_book_id}] miss market data."
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:147
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:148
 msgid "Order Cancelled: current bar [{order_book_id}] reach the limit_up price."
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:153
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:154
 msgid "Order Cancelled: current bar [{order_book_id}] reach the limit_down price."
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:160
-#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:166
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:161
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:167
 msgid "Order Cancelled: [{order_book_id}] has no liquidity."
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:175
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:176
 msgid "Order Cancelled: {order_book_id} bar no volume"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:191
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:192
 msgid ""
 "Order Cancelled: market order {order_book_id} volume {order_volume} due "
 "to volume limit"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:226
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:227
 msgid ""
 "Order Cancelled: market order {order_book_id} volume {order_volume} is "
 "larger than {volume_percent_limit} percent of current bar volume, fill "
 "{filled_volume} actually"
 msgstr ""
 
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:321
+msgid ""
+"Order Cancelled: market order {order_book_id} fill {filled_volume} "
+"actually"
+msgstr ""
+
 #: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:42
 msgid "invalid margin multiplier value: value range is (0, +∞]"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:51
-#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:58
+#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:52
+#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:59
 msgid "Not supported matching type {}"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:62
+#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:63
 msgid ""
 "matching_type = 'next_bar' is abandoned when frequency == '1d',Current "
 "matching_type is 'current_bar'."
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:105
+#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:108
 msgid "NO account_type = ({}) in {}"
 msgstr ""
 
@@ -474,28 +554,27 @@ msgstr ""
 msgid "You have traded {order_book_id} with {quantity} lots in limit down status"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:99
+#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:104
 msgid "unsupported position_effect {}"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:120
+#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:125
 msgid "{order_id} order has been cancelled by user."
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:136
+#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:141
 msgid "Order Rejected: {order_book_id} can not match. Market close."
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_event_source.py:44
-#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_event_source.py:72
-msgid "Unsupported market {}"
+#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:178
+msgid "{order_book_id} should be subscribed when frequency is tick."
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_event_source.py:52
+#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_event_source.py:46
 msgid "Current universe is empty. Please use subscribe function before trade"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_event_source.py:210
+#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_event_source.py:196
 msgid "Frequency {} is not support."
 msgstr ""
 
@@ -521,7 +600,7 @@ msgid ""
 " +∞)"
 msgstr ""
 
-#: rqalpha/model/bar.py:374
+#: rqalpha/model/bar.py:371
 msgid "id_or_symbols {} does not exist"
 msgstr ""
 
@@ -540,15 +619,15 @@ msgstr ""
 msgid "Cash add {}. units {} become to {}"
 msgstr ""
 
-#: rqalpha/portfolio/account.py:304
+#: rqalpha/portfolio/account.py:309
 msgid "Trigger Forced Liquidation, current total_value is 0"
 msgstr ""
 
-#: rqalpha/portfolio/account.py:425
+#: rqalpha/portfolio/account.py:431
 msgid "insufficient cash, current {}, target withdrawal {}"
 msgstr ""
 
-#: rqalpha/portfolio/position.py:175
+#: rqalpha/portfolio/position.py:169
 msgid "last price of position {} is not supposed to be nan"
 msgstr ""
 
@@ -654,40 +733,36 @@ msgstr ""
 msgid "in parse_user_config, exception: {e}"
 msgstr ""
 
-#: rqalpha/utils/config.py:207
+#: rqalpha/utils/config.py:206
 msgid "Invalid future info: underlying_symbol {} is illegal."
 msgstr ""
 
-#: rqalpha/utils/config.py:222
+#: rqalpha/utils/config.py:221
 msgid ""
 "Invalid future info: commission_type is suppose to be BY_MONEY or "
 "BY_VOLUME"
 msgstr ""
 
-#: rqalpha/utils/config.py:226
+#: rqalpha/utils/config.py:225
 msgid "Invalid future info: field {} is not valid"
 msgstr ""
 
-#: rqalpha/utils/config.py:256
+#: rqalpha/utils/config.py:255
 msgid "invalid init position {}, should be in format 'order_book_id:quantity'"
 msgstr ""
 
-#: rqalpha/utils/config.py:261
+#: rqalpha/utils/config.py:260
 msgid "invalid quantity for instrument {order_book_id}: {quantity}"
 msgstr ""
 
-#: rqalpha/utils/config.py:276
+#: rqalpha/utils/config.py:275
 msgid "unknown run type: {}"
 msgstr ""
 
-#: rqalpha/utils/config.py:289
+#: rqalpha/utils/config.py:288
 msgid "unknown persist mode: {}"
 msgstr ""
 
-#: rqalpha/utils/config.py:302
-msgid "unknown market type: {}"
-msgstr ""
-
 #: rqalpha/utils/functools.py:50
 msgid ""
 "function {}: invalid {} argument, expected an order_book_id or instrument"
diff --git a/rqalpha/__init__.py b/rqalpha/__init__.py
index 660e14a79..a55557d0d 100644
--- a/rqalpha/__init__.py
+++ b/rqalpha/__init__.py
@@ -4,17 +4,22 @@
 # 除非遵守当前许可，否则不得使用本软件。
 #
 #     * 非商业用途（非商业用途指个人出于非商业目的使用本软件，或者高校、研究所等非营利机构出于教育、科研等目的使用本软件）：
-#         遵守 Apache License 2.0（下称“Apache 2.0 许可”），您可以在以下位置获得 Apache 2.0 许可的副本：http://www.apache.org/licenses/LICENSE-2.0。
+#         遵守 Apache License 2.0（下称“Apache 2.0 许可”），
+#         您可以在以下位置获得 Apache 2.0 许可的副本：http://www.apache.org/licenses/LICENSE-2.0。
 #         除非法律有要求或以书面形式达成协议，否则本软件分发时需保持当前许可“原样”不变，且不得附加任何条件。
 #
 #     * 商业用途（商业用途指个人出于任何商业目的使用本软件，或者法人或其他组织出于任何目的使用本软件）：
-#         未经米筐科技授权，任何个人不得出于任何商业目的使用本软件（包括但不限于向第三方提供、销售、出租、出借、转让本软件、本软件的衍生产品、引用或借鉴了本软件功能或源代码的产品或服务），任何法人或其他组织不得出于任何目的使用本软件，否则米筐科技有权追究相应的知识产权侵权责任。
+#         未经米筐科技授权，任何个人不得出于任何商业目的使用本软件（包括但不限于向第三方提供、销售、出租、出借、转让本软件、
+#         本软件的衍生产品、引用或借鉴了本软件功能或源代码的产品或服务），任何法人或其他组织不得出于任何目的使用本软件，
+#         否则米筐科技有权追究相应的知识产权侵权责任。
 #         在此前提下，对本软件的使用同样需要遵守 Apache 2.0 许可，Apache 2.0 许可与本许可冲突之处，以本许可为准。
 #         详细的授权流程，请联系 public@ricequant.com 获取。
 
 """
 RQAlpha - a Algorithm Trading System
 """
+from typing import Optional
+
 from rqalpha.cmds import cli
 from rqalpha.api import export_as_api
 from rqalpha.apis import subscribe_event
@@ -59,6 +64,26 @@ def run_ipython_cell(line, cell=None):
 
 
 def run_file(strategy_file_path, config=None):
+    # type: (str, Optional[dict]) -> dict
+    """
+    传入策略文件路径运行回测。
+
+    :param strategy_file_path: 策略文件路径
+    :param config: 策略配置项字典，默认为空，此处传入的配置项优先级高于策略内 :code:`__config__` 中的配置项
+
+    :example:
+
+    .. code-block:: python
+
+        config = {
+            "base": {
+                ...
+            }
+        }
+
+        run_file("strategy.py", config=config)
+
+    """
     from rqalpha.utils.config import parse_config
     from rqalpha.utils.functools import clear_all_cached_functions
     from rqalpha import main
@@ -83,6 +108,33 @@ def run_file(strategy_file_path, config=None):
 
 
 def run_code(code, config=None):
+    # type: (str, Optional[dict]) -> dict
+    """
+    传入字符串形式的策略代码以运行回测。
+
+    :param code: 策略代码字符串
+    :param config:  策略配置项字典，默认为空，此处传入的配置项优先级高于策略内 :code:`__config__` 中的配置项
+
+    :example:
+
+    .. code-block:: python
+
+        config = {
+            "base": {
+                ...
+            }
+        }
+
+        CODE = \"\"\"
+        def init(context):
+            ...
+
+        def handle_bar(context, bar_dict):
+            ...
+        \"\"\"
+
+        run_code(CODE, config=config)
+    """
     from rqalpha.utils.config import parse_config
     from rqalpha.utils.functools import clear_all_cached_functions
     from rqalpha import main
@@ -101,6 +153,39 @@ def run_code(code, config=None):
 
 
 def run_func(**kwargs):
+    """
+    传入约定函数和策略配置运行回测。约定函数详见 API 手册约定函数部分，可用的配置项详见参数配置部分。
+
+    :Keyword Arguments:
+        * **config** (dict) -- 策略配置字典
+        * **init** (Callable[[:class:`~rqalpha.core.strategy_context.StrategyContext`], Any]) -- 策略初始化函数，会在策略开始运行时被调用，仅会执行一次
+        * **before_trading** (Callable[[:class:`~rqalpha.core.strategy_context.StrategyContext`], Any]) -- 盘前函数，会在每日盘前被调用一次
+        * **open_auction** (Callable[[:class:`~rqalpha.core.strategy_context.StrategyContext`, dict[str, :class:`~rqalpha.model.bar.BarObject`]], Any]) -- 集合竞价函数，会在每日盘前集合竞价阶段被调用一次
+        * **handle_bar** (Callable[[:class:`~rqalpha.core.strategy_context.StrategyContext`, dict[str, :class:`~rqalpha.model.bar.BarObject`]], Any]) -- k 线处理函数，会在盘中 k 线发生更新时被调用，适用于日/分钟级别回测
+        * **handle_tick** (Callable[[:class:`~rqalpha.core.strategy_context.StrategyContext`, :class:`~rqalpha.model.tick.TickObject`], Any]) -- 快照数据处理函数，会在每个 tick 到达时被调用，适用于 tick 回测
+        * **after_trading** (Callable[[:class:`~rqalpha.core.strategy_context.StrategyContext`], Any]) -- 盘后函数，会在每日交易结束后被调用一次
+
+    :return: dict
+
+    :example:
+
+    .. code-block:: python
+
+        config = {
+            "base": {
+                ...
+            }
+        }
+
+        def init(context):
+            ...
+
+        def handle_bar(context, bar_dict):
+            ...
+
+        run_func(config=config, init=init, handle_bar=handle_bar)
+
+    """
     from rqalpha.utils.functools import clear_all_cached_functions
     from rqalpha.utils.config import parse_config
     from rqalpha import main
diff --git a/rqalpha/apis/api_abstract.py b/rqalpha/apis/api_abstract.py
index f1a6b4920..a5100fab9 100644
--- a/rqalpha/apis/api_abstract.py
+++ b/rqalpha/apis/api_abstract.py
@@ -204,7 +204,7 @@ def order_target_percent(id_or_ins, percent, price=None, style=None):
 
     投资组合价值等于所有已有仓位的价值和剩余现金的总和。买/卖单会被下舍入一手股数（A股是100的倍数）的倍数。目标百分比应该是一个小数，并且最大值应该<=1，比如0.5表示50%。
 
-    如果position_to_adjust 计算之后是正的，那么会买入该证券，否则会卖出该证券。 需要注意，如果资金不足，该API将不会创建发送订单。
+    如果 position_to_adjust 计算之后是正的，那么会买入该证券，否则会卖出该证券。需要注意，如果需要买入证券而资金不足，该 API 将使用最大可用资金发出订单。
 
     :param id_or_ins: 下单标的物
     :param percent: 仓位最终所占投资组合总价值的目标百分比。
diff --git a/rqalpha/apis/api_base.py b/rqalpha/apis/api_base.py
index faef818ff..195be1372 100644
--- a/rqalpha/apis/api_base.py
+++ b/rqalpha/apis/api_base.py
@@ -17,7 +17,7 @@
 from __future__ import division
 
 import types
-from collections import Iterable
+from collections.abc import Iterable
 from datetime import date, datetime
 from typing import Callable, List, Optional, Union
 
@@ -373,7 +373,7 @@ def get_yield_curve(date=None, tenor=None):
 @apply_rules(
     verify_that("order_book_id", pre_check=True).is_listed_instrument(),
     verify_that("bar_count").is_instance_of(int).is_greater_than(0),
-    verify_that("frequency").is_valid_frequency(),
+    verify_that("frequency", pre_check=True).is_valid_frequency(),
     verify_that("fields").are_valid_fields(
         names.VALID_HISTORY_FIELDS, ignore_none=True
     ),
@@ -463,9 +463,6 @@ def history_bars(
     env = Environment.get_instance()
     dt = env.calendar_dt
 
-    if frequency[-1] not in {"m", "d", "w"}:
-        raise RQInvalidArgument("invalid frequency {}".format(frequency))
-
     if frequency[-1] == "m" and env.config.base.frequency == "1d":
         raise RQInvalidArgument("can not get minute history in day back test")
 
@@ -783,7 +780,7 @@ def current_snapshot(id_or_symbol):
 def get_positions():
     # type: () -> List[Position]
     """
-    获取所有持仓对象列表，
+    获取所有持仓对象列表。
 
     :example:
 
@@ -791,8 +788,8 @@ def get_positions():
 
         [In] get_positions()
         [Out]
-        [BookingPosition({'order_book_id': '000014.XSHE', 'quantity': 100, 'direction': POSITION_DIRECTION.LONG, 'old_quantity': 0, 'trading_pnl': 1.0, 'avg_price': 9.56, 'last_price': 0, 'position_pnl': 0.0}),
-         BookingPosition({'order_book_id': '000010.XSHE', 'quantity': 100, 'direction': POSITION_DIRECTION.LONG, 'old_quantity': 0, 'trading_pnl': 0.0, 'avg_price': 3.09, 'last_price': 0, 'position_pnl': 0.0})]
+        [StockPosition(order_book_id=000001.XSHE, direction=LONG, quantity=1000, market_value=19520.0, trading_pnl=0.0, position_pnl=0),
+        StockPosition(order_book_id=RB2112, direction=SHORT, quantity=2, market_value=-111580.0, trading_pnl=0.0, position_pnl=0)]
 
     """
     portfolio = Environment.get_instance().portfolio
@@ -806,7 +803,7 @@ def get_positions():
 def get_position(order_book_id, direction=POSITION_DIRECTION.LONG):
     # type: (str, Optional[POSITION_DIRECTION]) -> Position
     """
-    获取某个标的的持仓对象，
+    获取某个标的的持仓对象。
 
     :param order_book_id: 标的编号
     :param direction: 持仓方向
@@ -815,9 +812,9 @@ def get_position(order_book_id, direction=POSITION_DIRECTION.LONG):
 
     ..  code-block:: python3
 
-        [In] get_position('000014.XSHE','long_positions")
+        [In] get_position('000001.XSHE', POSITION_DIRECTION.LONG)
         [Out]
-        [BookingPosition({'order_book_id': '000014.XSHE', 'quantity': 100, 'direction': POSITION_DIRECTION.LONG, 'old_quantity': 0, 'trading_pnl': 1.0, 'avg_price': 9.56, 'last_price': 0, 'position_pnl': 0.0})]
+        StockPosition(order_book_id=000001.XSHE, direction=LONG, quantity=268600, market_value=4995960.0, trading_pnl=0.0, position_pnl=0)
 
     """
     portfolio = Environment.get_instance().portfolio
diff --git a/rqalpha/const.py b/rqalpha/const.py
index 4afcb6a90..4b0b58154 100644
--- a/rqalpha/const.py
+++ b/rqalpha/const.py
@@ -204,6 +204,3 @@ class EXCHANGE(CustomEnum):
 class TRADING_CALENDAR_TYPE(CustomEnum):
     EXCHANGE = "EXCHANGE"
     INTER_BANK = "INTERBANK"
-
-
-UNDERLYING_SYMBOL_PATTERN = "([a-zA-Z]+)\d+"
diff --git a/rqalpha/core/events.py b/rqalpha/core/events.py
index f958f1999..13b39cba1 100644
--- a/rqalpha/core/events.py
+++ b/rqalpha/core/events.py
@@ -31,6 +31,10 @@ def __init__(self):
         self._user_listeners = defaultdict(list)
 
     def add_listener(self, event_type, listener, user=False):
+        """
+        为指定的事件类型注册处理函数
+            注意！对于 Order/Trade/Position 等可能随时会被回收的对象，不应注册其绑定方法为事件处理函数
+        """
         (self._user_listeners if user else self._listeners)[event_type].append(listener)
 
     def prepend_listener(self, event_type, listener, user=False):
diff --git a/rqalpha/data/data_proxy.py b/rqalpha/data/data_proxy.py
index 5c97d7082..5f6965b1c 100644
--- a/rqalpha/data/data_proxy.py
+++ b/rqalpha/data/data_proxy.py
@@ -62,6 +62,7 @@ def get_yield_curve(self, start_date, end_date, tenor=None):
 
     def get_risk_free_rate(self, start_date, end_date):
         tenor = risk_free_helper.get_tenor_for(start_date, end_date)
+        # 为何取 start_date 当日的？表示 start_date 时借入资金、end_date 归还的成本
         yc = self._data_source.get_yield_curve(start_date, start_date, [tenor])
         if yc is None or yc.empty:
             return 0
diff --git a/rqalpha/mod/rqalpha_mod_sys_accounts/README.rst b/rqalpha/mod/rqalpha_mod_sys_accounts/README.rst
index 9aa9ff720..c27fe2f2f 100644
--- a/rqalpha/mod/rqalpha_mod_sys_accounts/README.rst
+++ b/rqalpha/mod/rqalpha_mod_sys_accounts/README.rst
@@ -24,17 +24,17 @@ RQAlpha 扩展账户 Mod，实现了股票和期货的账户逻辑，提供了
 ..  code-block:: python
 
     {
-        # 开启/关闭 股票 T+1， 默认开启
+        # 是否开启股票 T+1 限制
         "stock_t1": True,
-        # 分红再投资
+        # 是否开启自动分红再投资
         "dividend_reinvestment": False,
-        # 当持仓股票退市时，按照退市价格返还现金
+        # 当持仓股票退市时，是否按照退市价格返还现金
         "cash_return_by_stock_delisted": True,
         # 股票下单因资金不足被拒时改为使用全部剩余资金下单
         "auto_switch_order_value": False,
-        # 检查股票可平仓位是否充足
+        # 开启对股票仓位是否能满足平仓需求的检查
         "validate_stock_position": True,
-        # 检查期货可平仓位是否充足
+        # 开启对期货仓位是否能满足平仓需求的检查
         "validate_future_position": True,
     }
 
diff --git a/rqalpha/mod/rqalpha_mod_sys_accounts/__init__.py b/rqalpha/mod/rqalpha_mod_sys_accounts/__init__.py
index e0cc15875..7b8d5edef 100644
--- a/rqalpha/mod/rqalpha_mod_sys_accounts/__init__.py
+++ b/rqalpha/mod/rqalpha_mod_sys_accounts/__init__.py
@@ -17,17 +17,17 @@
 
 
 __config__ = {
-    # 开启/关闭 股票 T+1， 默认开启
+    # 是否开启股票 T+1 限制
     "stock_t1": True,
-    # 分红再投资
+    # 是否开启自动分红再投资
     "dividend_reinvestment": False,
-    # 当持仓股票退市时，按照退市价格返还现金
+    # 当持仓股票退市时，是否按照退市价格返还现金
     "cash_return_by_stock_delisted": True,
     # 股票下单因资金不足被拒时改为使用全部剩余资金下单
     "auto_switch_order_value": False,
-    # 检查股票可平仓位是否充足
+    # 开启对股票仓位是否能满足平仓需求的检查
     "validate_stock_position": True,
-    # 检查期货可平仓位是否充足
+    # 开启对期货仓位是否能满足平仓需求的检查
     "validate_future_position": True,
 }
 
@@ -63,7 +63,7 @@ def load_mod():
             cli_prefix + 'cash_return_by_stock_delisted'
         ),
         default=True,
-        help="[sys_simulation] return cash when stock delisted"
+        help="[sys_accounts] return cash when stock delisted"
     )
 )
 
@@ -72,6 +72,6 @@ def load_mod():
     click.Option(
         ("--no-short-stock/--short-stock", cli_prefix + "validate_stock_position"),
         is_flag=True, default=True,
-        help="[sys_simulation] enable stock shorting"
+        help="[sys_accounts] enable stock shorting"
     )
 )
diff --git a/rqalpha/mod/rqalpha_mod_sys_analyser/README.rst b/rqalpha/mod/rqalpha_mod_sys_analyser/README.rst
index 91571a8f7..15869efd1 100644
--- a/rqalpha/mod/rqalpha_mod_sys_analyser/README.rst
+++ b/rqalpha/mod/rqalpha_mod_sys_analyser/README.rst
@@ -29,18 +29,20 @@ RQAlpha 策略分析 Mod。
 ..  code-block:: python
 
    {
-        # 当不输出csv/pickle/plot 等内容时，可以通过 record 来决定是否执行该 Mod 的计算逻辑
+        # 策略基准，该基准将用于风险指标计算和收益曲线图绘制
+        #   若基准为单指数/股票，此处直接设置 order_book_id，如："000300.XSHG"
+        #   若基准为复合指数，则需传入 order_book_id 和权重构成的字典，如：{"000300.XSHG": 0.2. "000905.XSHG": 0.8}
+        "benchmark": None,
+        # 当不输出 csv/pickle/plot 等内容时，关闭该项可关闭策略运行过程中部分收集数据的逻辑，用以提升性能
         "record": True,
-        # 如果指定路径，则输出计算后的 pickle 文件
+        # 回测结果输出的文件路径，该文件为 pickle 格式，内容为每日净值、头寸、流水及风险指标等；若不设置则不输出该文件
         "output_file": None,
-        # 如果指定路径，则输出 report csv 文件
+        # 回测报告的数据目录，报告为 csv 格式；若不设置则不输出报告
         "report_save_path": None,
-        # 画图
+        # 是否在回测结束后绘制收益曲线图
         'plot': False,
-        # 如果指定路径，则输出 plot 对应的图片文件
+        # 收益曲线图路径，若设置则将收益曲线图保存为 png 文件
         'plot_save_file': None,
-        # 基准 order_book_id
-        "benchmark": None
     }
 
 您可以通过如下方式来修改模块的配置信息，比如下面的示例中介绍了如何开启显示回测收益曲线图
diff --git a/rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py b/rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py
index 38b7eeb08..69510da3a 100644
--- a/rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py
+++ b/rqalpha/mod/rqalpha_mod_sys_analyser/__init__.py
@@ -16,18 +16,20 @@
 from rqalpha import cli
 
 __config__ = {
-    # 当不输出csv/pickle/plot 等内容时，可以通过 record 来决定是否执行该 Mod 的计算逻辑
+    # 策略基准，该基准将用于风险指标计算和收益曲线图绘制
+    #   若基准为单指数/股票，此处直接设置 order_book_id，如："000300.XSHG"
+    #   若基准为复合指数，则需传入 order_book_id 和权重构成的字典，如：{"000300.XSHG": 0.2. "000905.XSHG": 0.8}
+    "benchmark": None,
+    # 当不输出 csv/pickle/plot 等内容时，关闭该项可关闭策略运行过程中部分收集数据的逻辑，用以提升性能
     "record": True,
-    # 如果指定路径，则输出计算后的 pickle 文件
+    # 回测结果输出的文件路径，该文件为 pickle 格式，内容为每日净值、头寸、流水及风险指标等；若不设置则不输出该文件
     "output_file": None,
-    # 如果指定路径，则输出 report csv 文件
+    # 回测报告的数据目录，报告为 csv 格式；若不设置则不输出报告
     "report_save_path": None,
-    # 画图
+    # 是否在回测结束后绘制收益曲线图
     'plot': False,
-    # 如果指定路径，则输出 plot 对应的图片文件
+    # 收益曲线图路径，若设置则将收益曲线图保存为 png 文件
     'plot_save_file': None,
-    # 基准 order_book_id
-    "benchmark": {}
 }
 
 
diff --git a/rqalpha/mod/rqalpha_mod_sys_analyser/mod.py b/rqalpha/mod/rqalpha_mod_sys_analyser/mod.py
index 4741d91b1..44430f288 100644
--- a/rqalpha/mod/rqalpha_mod_sys_analyser/mod.py
+++ b/rqalpha/mod/rqalpha_mod_sys_analyser/mod.py
@@ -16,6 +16,7 @@
 #         详细的授权流程，请联系 public@ricequant.com 获取。
 
 import os
+import pandas
 import pickle
 import jsonpickle
 import numbers
@@ -26,7 +27,7 @@
 
 import numpy as np
 import pandas as pd
-from rqrisk import Risk
+from rqrisk import Risk, WEEKLY
 
 from rqalpha.const import EXIT_CODE, DEFAULT_ACCOUNT_TYPE, INSTRUMENT_TYPE, POSITION_DIRECTION
 from rqalpha.core.events import EVENT
@@ -292,23 +293,26 @@ def tear_down(self, code, exception=None):
         for account_type, starting_cash in self._env.config.base.accounts.items():
             summary[account_type] = starting_cash
 
+        risk_free_rate = data_proxy.get_risk_free_rate(self._env.config.base.start_date, self._env.config.base.end_date)
         risk = Risk(
-            np.array(self._portfolio_daily_returns),
-            np.array(self._benchmark_daily_returns),
-            data_proxy.get_risk_free_rate(
-                self._env.config.base.start_date, self._env.config.base.end_date
-            )
+            np.array(self._portfolio_daily_returns), np.array(self._benchmark_daily_returns), risk_free_rate
         )
         summary.update({
             'alpha': self._safe_convert(risk.alpha, 3),
             'beta': self._safe_convert(risk.beta, 3),
             'sharpe': self._safe_convert(risk.sharpe, 3),
+            'excess_sharpe': self._safe_convert(risk.excess_sharpe, 3),
             'information_ratio': self._safe_convert(risk.information_ratio, 3),
             'downside_risk': self._safe_convert(risk.annual_downside_risk, 3),
             'tracking_error': self._safe_convert(risk.annual_tracking_error, 3),
             'sortino': self._safe_convert(risk.sortino, 3),
             'volatility': self._safe_convert(risk.annual_volatility, 3),
+            'excess_volatility': self._safe_convert(risk.excess_volatility, 3),
+            'excess_annual_volatility': self._safe_convert(risk.excess_annual_volatility, 3),
             'max_drawdown': self._safe_convert(risk.max_drawdown, 3),
+            'excess_max_drawdown': self._safe_convert(risk.excess_max_drawdown),
+            'excess_returns': self._safe_convert(risk.excess_return_rate, 6),
+            'excess_annual_returns': self._safe_convert(risk.excess_annual_return, 6)
         })
 
         summary.update({
@@ -334,7 +338,8 @@ def tear_down(self, code, exception=None):
         df = pd.DataFrame(self._total_portfolios)
         df['date'] = pd.to_datetime(df['date'])
         total_portfolios = df.set_index('date').sort_index()
-
+        weekly_nav = df.resample("W", on="date").last().set_index("date").unit_net_value.dropna()
+        weekly_returns = (weekly_nav / weekly_nav.shift(1).fillna(1)).fillna(0) - 1
         result_dict = {
             'summary': summary,
             'trades': trades,
@@ -345,7 +350,21 @@ def tear_down(self, code, exception=None):
             df = pd.DataFrame(self._total_benchmark_portfolios)
             df['date'] = pd.to_datetime(df['date'])
             benchmark_portfolios = df.set_index('date').sort_index()
+            weekly_b_nav = df.resample("W", on="date").last().set_index("date").unit_net_value.dropna()
+            weekly_b_returns = (weekly_b_nav / weekly_b_nav.shift(1).fillna(1)).fillna(0) - 1
             result_dict['benchmark_portfolio'] = benchmark_portfolios
+        else:
+            weekly_b_returns = pandas.Series(index=weekly_returns.index)
+        weekly_risk = Risk(weekly_returns, weekly_b_returns, risk_free_rate, WEEKLY)
+        summary.update({
+            "weekly_alpha": weekly_risk.alpha,
+            "weekly_beta": weekly_risk.beta,
+            "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_max_drawdown": weekly_risk.max_drawdown,
+        })
 
         plots = self._plot_store.get_plots()
         if plots:
diff --git a/rqalpha/mod/rqalpha_mod_sys_analyser/plot.py b/rqalpha/mod/rqalpha_mod_sys_analyser/plot.py
index c0fd3ef56..e2452634d 100644
--- a/rqalpha/mod/rqalpha_mod_sys_analyser/plot.py
+++ b/rqalpha/mod/rqalpha_mod_sys_analyser/plot.py
@@ -4,26 +4,140 @@
 # 除非遵守当前许可，否则不得使用本软件。
 #
 #     * 非商业用途（非商业用途指个人出于非商业目的使用本软件，或者高校、研究所等非营利机构出于教育、科研等目的使用本软件）：
-#         遵守 Apache License 2.0（下称“Apache 2.0 许可”），您可以在以下位置获得 Apache 2.0 许可的副本：http://www.apache.org/licenses/LICENSE-2.0。
+#         遵守 Apache License 2.0（下称“Apache 2.0 许可”），
+#         您可以在以下位置获得 Apache 2.0 许可的副本：http://www.apache.org/licenses/LICENSE-2.0。
 #         除非法律有要求或以书面形式达成协议，否则本软件分发时需保持当前许可“原样”不变，且不得附加任何条件。
 #
 #     * 商业用途（商业用途指个人出于任何商业目的使用本软件，或者法人或其他组织出于任何目的使用本软件）：
-#         未经米筐科技授权，任何个人不得出于任何商业目的使用本软件（包括但不限于向第三方提供、销售、出租、出借、转让本软件、本软件的衍生产品、引用或借鉴了本软件功能或源代码的产品或服务），任何法人或其他组织不得出于任何目的使用本软件，否则米筐科技有权追究相应的知识产权侵权责任。
+#         未经米筐科技授权，任何个人不得出于任何商业目的使用本软件（包括但不限于向第三方提供、销售、出租、出借、转让本软件、
+#         本软件的衍生产品、引用或借鉴了本软件功能或源代码的产品或服务），任何法人或其他组织不得出于任何目的使用本软件，
+#         否则米筐科技有权追究相应的知识产权侵权责任。
 #         在此前提下，对本软件的使用同样需要遵守 Apache 2.0 许可，Apache 2.0 许可与本许可冲突之处，以本许可为准。
 #         详细的授权流程，请联系 public@ricequant.com 获取。
+import os
+from collections import namedtuple, ChainMap
+
+import numpy as np
+import pandas as pd
+from matplotlib import rcParams, gridspec, ticker, image as mpimg, pyplot as plt
+from matplotlib.font_manager import findfont, FontProperties
 
 import rqalpha
 from rqalpha.utils.logger import system_log
-from rqalpha.utils.i18n import gettext
-
-
-def max_ddd(arr):
+from rqalpha.utils.i18n import gettext as _
+
+plt.style.use('ggplot')
+
+rcParams['font.family'] = 'sans-serif'
+rcParams['font.sans-serif'] = [
+    'Microsoft Yahei', 'Heiti SC', 'Heiti TC', 'STHeiti', 'WenQuanYi Zen Hei',
+    'WenQuanYi Micro Hei', "文泉驿微米黑", 'SimHei', 'Arial Unicode MS'
+] + rcParams['font.sans-serif']
+rcParams['axes.unicode_minus'] = False
+
+font = findfont(FontProperties(family=['sans-serif']))
+if "/matplotlib/" in font:
+    system_log.warn("PLOT: Missing Chinese fonts. Fallback to English.")
+    LABEL_FONT_SIZE = 8
+    _ = lambda txt: txt
+else:
+    LABEL_FONT_SIZE = 10
+
+# 三部分的高度
+PLOT_AREA_HEIGHT = 4
+USER_PLOT_AREA_HEIGHT = 2
+
+IMG_WIDTH = 13
+
+Heights = namedtuple("Heights", ("label", "value"))
+Indicator = namedtuple("Indicator", ("key", "label", "color", "formatter", "value_font_size"))
+
+RED = "#aa4643"
+LIGHT_CORAL = "#F08080"
+BLUE = "#4572a7"
+YELLOW = "#F3A423"
+BLACK = "#000000"
+
+# 指标的高度值
+INDICATOR_Y_POS = [
+    Heights(1.3, 1.15),
+    Heights(0.95, 0.8),
+    Heights(0.6, 0.45),
+    Heights(0.25, 0.10),
+]
+INDICATOR_X_POS = [0.15 * i for i in range(7)]
+
+INDICATORS = [[
+    Indicator("total_returns", _("TotalReturns"), RED, "{0:.3%}", 11),
+    Indicator("annualized_returns", _("AnnualReturns"), RED, "{0:.3%}", 11),
+    Indicator("alpha", _("Alpha"), BLACK, "{0:.4}", 11),
+    Indicator("beta", _("Beta"), BLACK, "{0:.4}", 11),
+    Indicator("sharpe", _("Sharpe"), BLACK, "{0:.4}", 11),
+    Indicator("sortino", _("Sortino"), BLACK, "{0:.4}", 11),
+    Indicator("information_ratio", _("InformationRatio"), BLACK, "{0:.4}", 11),
+],[
+    Indicator("benchmark_total_returns", _("BenchmarkReturns"), BLUE, "{0:.3%}", 11),
+    Indicator("benchmark_annualized_returns", _("BenchmarkAnnual"), BLUE, "{0:.3%}", 11),
+    Indicator("volatility", _("Volatility"), BLACK, "{0:.4}", 11),
+    Indicator("max_drawdown", _("MaxDrawdown"), BLACK, "{0:.4}", 11),
+    Indicator("tracking_error", _("TrackingError"), BLACK, "{0:.4}", 11),
+    Indicator("downside_risk", _("DownsideRisk"), BLACK, "{0:.4}", 11),
+    Indicator("max_dd_ddd", _("MaxDD/MaxDDD"), BLACK, "{}", 6),
+], [
+    Indicator("weekly_alpha", _("WeeklyAlpha"), BLACK, "{0:.4}", 11),
+    Indicator("weekly_beta", _("WeeklyBeta"), BLACK, "{0:.4}", 11),
+    Indicator("weekly_sharpe", _("WeeklySharpe"), BLACK, "{0:.4}", 11),
+    Indicator("weekly_sortino", _("WeeklySortino"), BLACK, "{0:.4}", 11),
+    Indicator("weekly_information_ratio", _("WeeklyInfoRatio"), BLACK, "{0:.4}", 11),
+    Indicator("weekly_tracking_error", _("WeeklyTrackingError"), BLACK, "{0:.4}", 11),
+    Indicator("weekly_max_drawdown", _("WeeklyMaxDrawdown"), BLACK, "{0:.4}", 11),
+]]
+
+excess_indicators = [
+    Indicator("excess_returns", _("ExcessReturns"), RED, "{0:.3%}", 11),
+    Indicator("excess_annual_returns", _("ExcessAnnual"), RED, "{0:.3%}", 11),
+    Indicator("excess_sharpe", _("ExcessSharpe"), BLACK, "{0:.4}", 11),
+    Indicator("excess_volatility", _("ExcessVolatility"), BLACK, "{0:.4}", 11),
+    Indicator("excess_annual_volatility", _("ExcessAnnualVolatility"), BLACK, "{0:.4}", 11),
+    Indicator("excess_max_drawdown", _("ExcessMaxDD"), BLACK, "{0:.4}", 11),
+    Indicator("excess_max_dd_ddd", _("ExcessMaxDD/ExcessMaxDDD"), BLACK, "{}", 6),
+]
+
+
+class IndexRange(namedtuple("IndexRange", ("start", "end", "start_date", "end_date"))):
+    @classmethod
+    def new(cls, start, end, index):
+        return cls(start, end, index[start].date(), index[end].date())
+
+    @property
+    def repr(self):
+        return "{}~{}, {} days".format(self.start_date, self.end_date, (self.end_date - self.start_date).days)
+
+    def plot(self, ax, returns, marker, color, label):
+        ax.plot(
+            [self.start_date, self.end_date], [returns[self.start], returns[self.end]],
+            marker, color=color, markersize=8, alpha=.7, label=label
+        )
+
+
+def _max_dd(arr, index):
+    # type: (np.array, pd.DatetimeIndex) -> IndexRange
+    end = np.argmax(np.maximum.accumulate(arr) / arr)
+    if end == 0:
+        end = len(arr) - 1
+    start = np.argmax(arr[:end]) if end > 0 else 0
+    return IndexRange.new(start, end, index)
+
+
+def _max_ddd(arr, index):
+    # type: (np.array, pd.DatetimeIndex) -> IndexRange
     max_seen = arr[0]
     ddd_start, ddd_end = 0, 0
     ddd = 0
     start = 0
     in_draw_down = False
 
+    i = 0
     for i in range(len(arr)):
         if arr[i] > max_seen:
             if in_draw_down:
@@ -40,163 +154,140 @@ def max_ddd(arr):
 
     if arr[i] < max_seen:
         if i - start > ddd:
-            return start, i
+            return IndexRange.new(start, i, index)
 
-    return ddd_start, ddd_end
+    return IndexRange.new(ddd_start, ddd_end, index)
 
 
-def plot_result(result_dict, show_windows=True, savefile=None):
-    import os
-    from matplotlib import rcParams, gridspec, ticker, image as mpimg, pyplot as plt
-    from matplotlib.font_manager import findfont, FontProperties
-    import numpy as np
-
-    rcParams['font.family'] = 'sans-serif'
-    rcParams['font.sans-serif'] = [
-        u'Microsoft Yahei',
-        u'Heiti SC',
-        u'Heiti TC',
-        u'STHeiti',
-        u'WenQuanYi Zen Hei',
-        u'WenQuanYi Micro Hei',
-        u"文泉驿微米黑",
-        u'SimHei',
-    ] + rcParams['font.sans-serif']
-    rcParams['axes.unicode_minus'] = False
-
-    use_chinese_fonts = True
-    font = findfont(FontProperties(family=['sans-serif']))
-    if "/matplotlib/" in font:
-        use_chinese_fonts = False
-        system_log.warn("Missing Chinese fonts. Fallback to English.")
+def _plot_indicators(ax, indicator_values, plot_excess_indicators):
+    ax.axis("off")
+    if plot_excess_indicators:
+        all_indicators = INDICATORS + [excess_indicators]
+    else:
+        all_indicators = [[]] + INDICATORS
+
+    for lineno, indicators in enumerate(all_indicators):
+        for index_in_line, i in enumerate(indicators):
+            x = INDICATOR_X_POS[index_in_line]
+            y = INDICATOR_Y_POS[lineno]
+            try:
+                value = i.formatter.format(indicator_values[i.key])
+            except KeyError:
+                value = "nan"
+            ax.text(x, y.label, i.label, color=i.color, fontsize=LABEL_FONT_SIZE),
+            ax.text(x, y.value, value, color=BLACK, fontsize=i.value_font_size)
+
+
+def _plot_returns(ax, returns, b_returns, excess_returns, weekly_returns, weekly_b_returns, max_dd, max_ddd):
+    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.patch.set_alpha(0.6)
+
+    # plot lines
+    ax.plot(returns, label=_(u"Strategy"), alpha=1, linewidth=2, color=RED)
+    if b_returns is not None:
+        ax.plot(b_returns, label=_(u"Benchmark"), alpha=1, linewidth=2, color=BLUE)
+    if excess_returns is not None:
+        ax.plot(excess_returns, label=_(u"Excess"), alpha=1, linewidth=2, color=YELLOW)
+    ax.plot(weekly_returns, label=_("Weekly"), alpha=0.6, linewidth=2, color=RED)
+    if weekly_b_returns is not None:
+        ax.plot(weekly_b_returns, label=_("BenchmarkWeekly"), alpha=0.6, linewidth=2, color=BLUE)
 
-    summary = result_dict["summary"]
+    # plot MaxDD/MaxDDD
+    max_dd.plot(ax, returns, "v", "Green", _("MaxDrawdown"))
+    max_ddd.plot(ax, returns, "D", "Blue", _("MaxDDD"))
 
-    title = summary['strategy_file']
+    # place legend
+    leg = plt.legend(loc="best")
+    leg.get_frame().set_alpha(0.5)
 
-    portfolio = result_dict["portfolio"]
-    benchmark_portfolio = result_dict.get("benchmark_portfolio")
+    # manipulate axis
+    vals = ax.get_yticks()
+    ax.set_yticklabels(['{:3.2f}%'.format(x * 100) for x in vals])
 
-    index = portfolio.index
 
-    # max drawdown
-    portfolio_value = portfolio.unit_net_value * portfolio.units
-    xs = portfolio_value.values
-    rt = portfolio.unit_net_value.values
-    max_dd_end = np.argmax(np.maximum.accumulate(xs) / xs)
-    if max_dd_end == 0:
-        max_dd_end = len(xs) - 1
-    max_dd_start = np.argmax(xs[:max_dd_end]) if max_dd_end > 0 else 0
+def _plot_user_plots(ax, plots_df):
+    ax.patch.set_alpha(0.6)
+    for column in plots_df.columns:
+        ax.plot(plots_df[column], label=column)
 
-    max_ddd_start_day, max_ddd_end_day = max_ddd(xs)
-    max_dd_info = "MaxDD  {}~{}, {} days".format(index[max_dd_start], index[max_dd_end],
-                                                 (index[max_dd_end] - index[max_dd_start]).days)
-    max_dd_info += "\nMaxDDD {}~{}, {} days".format(index[max_ddd_start_day], index[max_ddd_end_day],
-                                                    (index[max_ddd_end_day] - index[max_ddd_start_day]).days)
+    leg = plt.legend(loc="best")
+    leg.get_frame().set_alpha(0.5)
 
-    plt.style.use('ggplot')
 
-    red = "#aa4643"
-    blue = "#4572a7"
-    black = "#000000"
+def plot_result(result_dict, show_windows=True, savefile=None):
+    summary = result_dict["summary"]
 
-    plots_area_size = 0
     if "plots" in result_dict:
-        plots_area_size = 5
-    img_width = 13
-    img_height = 6 + int(plots_area_size * 0.9)
+        indicator_area_height = 3
+        img_height = indicator_area_height + PLOT_AREA_HEIGHT + USER_PLOT_AREA_HEIGHT + 1
+    else:
+        indicator_area_height = 2
+        img_height = indicator_area_height + PLOT_AREA_HEIGHT
 
     logo_file = os.path.join(
         os.path.dirname(os.path.realpath(rqalpha.__file__)),
         "resource", 'ricequant-logo.png')
     logo_img = mpimg.imread(logo_file)
-    dpi = logo_img.shape[1] / img_width * 1.1
-
-    fig = plt.figure(title, figsize=(img_width, img_height), dpi=dpi)
-    gs = gridspec.GridSpec(10 + plots_area_size, 8)
-
-    # draw risk and portfolio
-    font_size = 12
-    value_font_size = 11
-    label_height, value_height = 0.8, 0.6
-    label_height2, value_height2 = 0.35, 0.15
-
-    def _(txt):
-        return gettext(txt) if use_chinese_fonts else txt
-
-    fig_data = [
-        (0.00, label_height, value_height, _(u"Total Returns"), "{0:.3%}".format(summary["total_returns"]), red, black),
-        (0.15, label_height, value_height, _(u"Annual Returns"), "{0:.3%}".format(summary["annualized_returns"]), red, black),
-        (0.00, label_height2, value_height2, _(u"Benchmark Returns"), "{0:.3%}".format(summary.get("benchmark_total_returns", 0)), blue,
-         black),
-        (0.15, label_height2, value_height2, _(u"Benchmark Annual"), "{0:.3%}".format(summary.get("benchmark_annualized_returns", 0)),
-         blue, black),
-
-        (0.30, label_height, value_height, _(u"Alpha"), "{0:.4}".format(summary["alpha"]), black, black),
-        (0.40, label_height, value_height, _(u"Beta"), "{0:.4}".format(summary["beta"]), black, black),
-        (0.55, label_height, value_height, _(u"Sharpe"), "{0:.4}".format(summary["sharpe"]), black, black),
-        (0.70, label_height, value_height, _(u"Sortino"), "{0:.4}".format(summary["sortino"]), black, black),
-        (0.85, label_height, value_height, _(u"Information Ratio"), "{0:.4}".format(summary["information_ratio"]), black, black),
-
-        (0.30, label_height2, value_height2, _(u"Volatility"), "{0:.4}".format(summary["volatility"]), black, black),
-        (0.40, label_height2, value_height2, _(u"MaxDrawdown"), "{0:.3%}".format(summary["max_drawdown"]), black, black),
-        (0.55, label_height2, value_height2, _(u"Tracking Error"), "{0:.4}".format(summary["tracking_error"]), black, black),
-        (0.70, label_height2, value_height2, _(u"Downside Risk"), "{0:.4}".format(summary["downside_risk"]), black, black),
-    ]
-
-    ax = plt.subplot(gs[:3, :-1])
-    ax.axis("off")
-    for x, y1, y2, label, value, label_color, value_color in fig_data:
-        ax.text(x, y1, label, color=label_color, fontsize=font_size)
-        ax.text(x, y2, value, color=value_color, fontsize=value_font_size)
-    for x, y1, y2, label, value, label_color, value_color in [
-        (0.85, label_height2, value_height2, _(u"MaxDD/MaxDDD"), max_dd_info, black, black)]:
-        ax.text(x, y1, label, color=label_color, fontsize=font_size)
-        ax.text(x, y2, value, color=value_color, fontsize=8)
+    dpi = logo_img.shape[1] / IMG_WIDTH * 1.1
 
-    # strategy vs benchmark
-    ax = plt.subplot(gs[4:10, :])
+    title = summary['strategy_file']
+    fig = plt.figure(title, figsize=(IMG_WIDTH, img_height), dpi=dpi)
 
-    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)
+    gs = gridspec.GridSpec(img_height, 8)
 
-    # plot two lines
-    ax.plot(portfolio["unit_net_value"] - 1.0, label=_(u"strategy"), alpha=1, linewidth=2, color=red)
+    portfolio = result_dict["portfolio"]
+    benchmark_portfolio = result_dict.get("benchmark_portfolio")
+    max_dd = _max_dd(portfolio.unit_net_value.values, portfolio.index)
+    max_ddd = _max_ddd(portfolio.unit_net_value.values, portfolio.index)
+    # 超额收益
     if benchmark_portfolio is not None:
-        ax.plot(benchmark_portfolio["unit_net_value"] - 1.0, label=_(u"benchmark"), alpha=1, linewidth=2, color=blue)
-
-    # plot MaxDD/MaxDDD
-    ax.plot([index[max_dd_end], index[max_dd_start]], [rt[max_dd_end] - 1.0, rt[max_dd_start] - 1.0],
-            'v', color='Green', markersize=8, alpha=.7, label=_(u"MaxDrawdown"))
-    ax.plot([index[max_ddd_start_day], index[max_ddd_end_day]],
-            [rt[max_ddd_start_day] - 1.0, rt[max_ddd_end_day] - 1.0], 'D', color='Blue', markersize=8, alpha=.7,
-            label=_(u"MaxDDD"))
-
-    # place legend
-    leg = plt.legend(loc="best")
-    leg.get_frame().set_alpha(0.5)
+        ex_returns = portfolio.unit_net_value - benchmark_portfolio.unit_net_value
+        ex_max_dd = _max_dd(ex_returns + 1, portfolio.index)
+        ex_max_ddd = _max_ddd(ex_returns + 1, portfolio.index)
+        ex_max_dd_ddd = "MaxDD {}\nMaxDDD {}".format(ex_max_dd.repr, ex_max_ddd.repr)
+        plot_excess_indicators = True
+        benchmark_weekly_portfolio = benchmark_portfolio.unit_net_value.reset_index().resample(
+            "W", on="date").last().set_index("date").unit_net_value.dropna() - 1
+    else:
+        ex_max_dd_ddd = "nan"
+        ex_returns = None
+        benchmark_weekly_portfolio = None
+        plot_excess_indicators = False
+
+    _plot_indicators(
+        plt.subplot(gs[:indicator_area_height, :-1]),
+        ChainMap(summary, {
+            "max_dd_ddd": "MaxDD {}\nMaxDDD {}".format(max_dd.repr, max_ddd.repr),
+            "excess_max_dd_ddd": ex_max_dd_ddd,
+        }),
+        plot_excess_indicators
+    )
 
-    # manipulate axis
-    vals = ax.get_yticks()
-    ax.set_yticklabels(['{:3.2f}%'.format(x * 100) for x in vals])
+    _plot_returns(
+        plt.subplot(gs[indicator_area_height: PLOT_AREA_HEIGHT + indicator_area_height, :]),
+        portfolio.unit_net_value - 1,
+        None if benchmark_portfolio is None else benchmark_portfolio["unit_net_value"] - 1,
+        ex_returns,
+        portfolio.unit_net_value.reset_index().resample(
+            "W", on="date").last().set_index("date").unit_net_value.dropna() - 1,
+        benchmark_weekly_portfolio,
+        max_dd,
+        max_ddd
+    )
 
-    # plot user plots
     if "plots" in result_dict:
-        plots_df = result_dict["plots"]
-
-        ax2 = plt.subplot(gs[11:, :])
-        for column in plots_df.columns:
-            ax2.plot(plots_df[column], label=column)
-
-        leg = plt.legend(loc="best")
-        leg.get_frame().set_alpha(0.5)
+        _plot_user_plots(
+            plt.subplot(gs[indicator_area_height + PLOT_AREA_HEIGHT + 1:, :]),
+            result_dict["plots"]
+        )
 
     # logo as watermark
     fig.figimage(
         logo_img,
-        xo=((img_width * dpi) - logo_img.shape[1]) / 2,
+        xo=((IMG_WIDTH * dpi) - logo_img.shape[1]) / 2,
         yo=(img_height * dpi - logo_img.shape[0]) / 2,
         alpha=0.4,
     )
@@ -209,3 +300,9 @@ def _(txt):
 
     if show_windows:
         plt.show()
+
+
+if __name__ == "__main__":
+    import pickle
+    with open("result.pkl", "rb") as f:
+        plot_result(pickle.loads(f.read()))
diff --git a/rqalpha/mod/rqalpha_mod_sys_analyser/plot_store.py b/rqalpha/mod/rqalpha_mod_sys_analyser/plot_store.py
index 1a7fb8bfc..a935b8e77 100644
--- a/rqalpha/mod/rqalpha_mod_sys_analyser/plot_store.py
+++ b/rqalpha/mod/rqalpha_mod_sys_analyser/plot_store.py
@@ -45,9 +45,18 @@ def get_plots(self):
     def plot(self, series_name, value):
         # type: (str, float) -> None
         """
-        在生成的图标结果中，某一个根线上增加一个点。
+        在策略运行结束后的收益图中，加入自定义的曲线。
+        每次调用 plot 函数将会以当前时间为横坐标，value 为纵坐标加入一个点，series_name 相同的点将连成一条曲线。
+
+        :param series_name: 曲线名称
+        :param value: 点的纵坐标值
+
+        :example:
+
+        .. code-block:: python
+
+            def handle_bar(context, bar_dict):
+                plot("OPEN", bar_dict["000001.XSHE"].open)
 
-        :param series_name: 序列名称
-        :param value: 值
         """
         self.add_plot(self._env.trading_dt.date(), series_name, value)
diff --git a/rqalpha/mod/rqalpha_mod_sys_progress/README.rst b/rqalpha/mod/rqalpha_mod_sys_progress/README.rst
index 52cffd330..8671b04a3 100644
--- a/rqalpha/mod/rqalpha_mod_sys_progress/README.rst
+++ b/rqalpha/mod/rqalpha_mod_sys_progress/README.rst
@@ -29,7 +29,8 @@ RQAlpha 控制台进度条 Mod
 ..  code-block:: python
 
     {
-        "show": False
+        # 是否在命令行/终端绘制进度条
+        "show": False,
     }
 
 您可以直接修改模块配置信息来选择开启/关闭对应功能
diff --git a/rqalpha/mod/rqalpha_mod_sys_progress/__init__.py b/rqalpha/mod/rqalpha_mod_sys_progress/__init__.py
index fb2df60b3..433eab159 100644
--- a/rqalpha/mod/rqalpha_mod_sys_progress/__init__.py
+++ b/rqalpha/mod/rqalpha_mod_sys_progress/__init__.py
@@ -17,7 +17,8 @@
 
 
 __config__ = {
-    "show": False
+    # 是否在命令行/终端绘制进度条
+    "show": False,
 }
 
 
diff --git a/rqalpha/mod/rqalpha_mod_sys_risk/README.rst b/rqalpha/mod/rqalpha_mod_sys_risk/README.rst
index 5176b60ae..7fffadb70 100644
--- a/rqalpha/mod/rqalpha_mod_sys_risk/README.rst
+++ b/rqalpha/mod/rqalpha_mod_sys_risk/README.rst
@@ -25,13 +25,13 @@ RQAlpha 风控 Mod
 ..  code-block:: python
 
     {
-        # 检查限价单价格是否合法
+        # 开启对限价单价格合法性的检查
         "validate_price": True,
-        # 检查标的证券是否可以交易
+        # 开启对标的可交易情况对检查
         "validate_is_trading": True,
-        # 检查可用资金是否充足
+        # 开启对可用资金是否足够满足下单要求的检查
         "validate_cash": True,
-        # 检查是否存在自成交的风险
+        # 开启对存在自成交风险的检查
         "validate_self_trade": False,
     }
 
diff --git a/rqalpha/mod/rqalpha_mod_sys_risk/__init__.py b/rqalpha/mod/rqalpha_mod_sys_risk/__init__.py
index d9ec47113..8c5ff4e77 100644
--- a/rqalpha/mod/rqalpha_mod_sys_risk/__init__.py
+++ b/rqalpha/mod/rqalpha_mod_sys_risk/__init__.py
@@ -17,13 +17,13 @@
 from rqalpha import cli
 
 __config__ = {
-    # 检查限价单价格是否合法
+    # 开启对限价单价格合法性的检查
     "validate_price": True,
-    # 检查标的证券是否可以交易
+    # 开启对标的可交易情况对检查
     "validate_is_trading": True,
-    # 检查可用资金是否充足
+    # 开启对可用资金是否足够满足下单要求的检查
     "validate_cash": True,
-    # 检查是否存在自成交的风险
+    # 开启对存在自成交风险的检查
     "validate_self_trade": False,
 }
 
diff --git a/rqalpha/mod/rqalpha_mod_sys_simulation/README.rst b/rqalpha/mod/rqalpha_mod_sys_simulation/README.rst
index e9c7597a4..3c1a21dc3 100644
--- a/rqalpha/mod/rqalpha_mod_sys_simulation/README.rst
+++ b/rqalpha/mod/rqalpha_mod_sys_simulation/README.rst
@@ -27,24 +27,32 @@ RQAlpha 回测 Mod，启用该模块开启回测功能。
 ..  code-block:: python
 
     {
-        # 是否开启信号模式
+        # 开启信号模式：该模式下，所有通过风控的订单将不进行撮合，直接产生交易
         "signal": False,
-        # 启用的回测引擎，目前支持 `current_bar` (当前Bar收盘价撮合) 和 `next_bar` (下一个Bar开盘价撮合)
+        # 撮合方式，其中：
+        #   日回测的可选值为 "current_bar"|"vwap"（以当前 bar 收盘价｜成交量加权平均价撮合）
+        #   分钟回测的可选值有 "current_bar"|"next_bar"|"vwap"（以当前 bar 收盘价｜下一个 bar 的开盘价｜成交量加权平均价撮合)
+        #   tick 回测的可选值有 "last"|"best_own"|"best_counterparty"（以最新价｜己方最优价｜对手方最优价撮合）和 "counterparty_offer"（逐档撮合）
         "matching_type": "current_bar",
-        # price_limit: 在处于涨跌停时，无法买进/卖出，默认开启【在 Signal 模式下，不再禁止买进/卖出，如果开启，则给出警告提示。】
+        # 开启对于处于涨跌停状态的证券的撮合限制（信号模式下该限制不生效）
         "price_limit": True,
-        # liquidity_limit: 当对手盘没有流动性的时候，无法买进/卖出，默认关闭
+        # 开启对于对手盘无流动性的证券的撮合限制（仅在 tick 回测下生效）
         "liquidity_limit": False,
-        # 是否有成交量限制
+        # 开启成交量限制（仅在日和分钟回测下生效）
+        #   开启该限制意味着每个 bar 的累计成交量将不会超过该时间段内市场上总成交量的一定比值（volume_percent）
         "volume_limit": True,
-        # 按照当前成交量的百分比进行撮合
+        # 每个 bar 可成交数量占市场总成交量的比值，在 volume_limit 开启时生效
         "volume_percent": 0.25,
-        # 滑点模型，如果使用自己的定制的滑点，需要加上完整的包名
+        # 滑点模型，可选值有 "PriceRatioSlippage"（按价格比例设置滑点）和 "TickSizeSlippage"（按跳设置滑点）
+        #    亦可自己实现滑点模型，选择自己实现的滑点模型时，此处需传入包含包和模块的完整类路径
+        #    滑点模型类需继承自 rqalpha.mod.rqalpha_mod_sys_simulation.slippage.BaseSlippage
         "slippage_model": "PriceRatioSlippage",
-        # 设置滑点
+        # 设置滑点值，对于 PriceRatioSlippage 表示价格的比例，对于 TickSizeSlippage 表示跳的数量
         "slippage": 0,
-         # volume 为 0 时限制成交
+        # 开启对于当前 bar 无成交量的标的的撮合限制（仅在日和分钟回测下生效）
         "inactive_limit": True,
+        # 账户每日计提的费用，需按照(账户类型，费率)的格式传入，例如[("STOCK", 0.0001), ("FUTURE", 0.0001)]
+        "management_fee": [],
     }
 
 您可以通过如下方式来修改模块的配置信息，比如下面的示例中介绍了如何设置滑点
diff --git a/rqalpha/mod/rqalpha_mod_sys_simulation/__init__.py b/rqalpha/mod/rqalpha_mod_sys_simulation/__init__.py
index 927b55a0a..3bdcd27cc 100644
--- a/rqalpha/mod/rqalpha_mod_sys_simulation/__init__.py
+++ b/rqalpha/mod/rqalpha_mod_sys_simulation/__init__.py
@@ -17,26 +17,32 @@
 from rqalpha import cli
 
 __config__ = {
-    # 是否开启信号模式
+    # 开启信号模式：该模式下，所有通过风控的订单将不进行撮合，直接产生交易
     "signal": False,
-    # 启用的回测引擎，目前支持 `current_bar` (当前Bar收盘价撮合) 和 `next_bar` (下一个Bar开盘价撮合)
+    # 撮合方式，其中：
+    #   日回测的可选值为 "current_bar"|"vwap"（以当前 bar 收盘价｜成交量加权平均价撮合）
+    #   分钟回测的可选值有 "current_bar"|"next_bar"|"vwap"（以当前 bar 收盘价｜下一个 bar 的开盘价｜成交量加权平均价撮合)
+    #   tick 回测的可选值有 "last"|"best_own"|"best_counterparty"（以最新价｜己方最优价｜对手方最优价撮合）和 "counterparty_offer"（逐档撮合）
     "matching_type": "current_bar",
-    # price_limit: 在处于涨跌停时，无法买进/卖出，默认开启【在 Signal 模式下，不再禁止买进/卖出，如果开启，则给出警告提示。】
+    # 开启对于处于涨跌停状态的证券的撮合限制（信号模式下该限制不生效）
     "price_limit": True,
-    # liquidity_limit: 当对手盘没有流动性的时候，无法买进/卖出，默认关闭
+    # 开启对于对手盘无流动性的证券的撮合限制（仅在 tick 回测下生效）
     "liquidity_limit": False,
-    # 是否有成交量限制
+    # 开启成交量限制（仅在日和分钟回测下生效）
+    #   开启该限制意味着每个 bar 的累计成交量将不会超过该时间段内市场上总成交量的一定比值（volume_percent）
     "volume_limit": True,
-    # 按照当前成交量的百分比进行撮合
+    # 每个 bar 可成交数量占市场总成交量的比值，在 volume_limit 开启时生效
     "volume_percent": 0.25,
-    # 滑点模型，如果使用自己的定制的滑点，需要加上完整的包名
+    # 滑点模型，可选值有 "PriceRatioSlippage"（按价格比例设置滑点）和 "TickSizeSlippage"（按跳设置滑点）
+    #    亦可自己实现滑点模型，选择自己实现的滑点模型时，此处需传入包含包和模块的完整类路径
+    #    滑点模型类需继承自 rqalpha.mod.rqalpha_mod_sys_simulation.slippage.BaseSlippage
     "slippage_model": "PriceRatioSlippage",
-    # 设置滑点
+    # 设置滑点值，对于 PriceRatioSlippage 表示价格的比例，对于 TickSizeSlippage 表示跳的数量
     "slippage": 0,
-    # volume 为 0 时限制成交
+    # 开启对于当前 bar 无成交量的标的的撮合限制（仅在日和分钟回测下生效）
     "inactive_limit": True,
-    # 账户级别每日的管理费用
-    "management_fee": (),
+    # 账户每日计提的费用，需按照(账户类型，费率)的格式传入，例如[("STOCK", 0.0001), ("FUTURE", 0.0001)]
+    "management_fee": [],
 }
 
 
diff --git a/rqalpha/mod/rqalpha_mod_sys_transaction_cost/README.rst b/rqalpha/mod/rqalpha_mod_sys_transaction_cost/README.rst
index 71d6a768e..dd0f34139 100644
--- a/rqalpha/mod/rqalpha_mod_sys_transaction_cost/README.rst
+++ b/rqalpha/mod/rqalpha_mod_sys_transaction_cost/README.rst
@@ -24,10 +24,12 @@ RQAlpha 交易税费 Mod，实现了不同市场不同交易标的的税费计
 .. code-block:: python
 
     {
-        # A股最小手续费
+        # 股票最小手续费，单位元
         "cn_stock_min_commission": 5,
-        # 设置手续费乘数，默认为1
+        # 佣金倍率，即在默认的手续费率基础上按该倍数进行调整，股票的默认佣金为万八，期货默认佣金因合约而异
         "commission_multiplier": 1,
+        # 印花倍率，即在默认的印花税基础上按该倍数进行调整，股票默认印花税为千分之一，单边收取
+        "tax_multiplier": 1,
     }
 
 
diff --git a/rqalpha/mod/rqalpha_mod_sys_transaction_cost/__init__.py b/rqalpha/mod/rqalpha_mod_sys_transaction_cost/__init__.py
index 00e600753..db16a87a2 100644
--- a/rqalpha/mod/rqalpha_mod_sys_transaction_cost/__init__.py
+++ b/rqalpha/mod/rqalpha_mod_sys_transaction_cost/__init__.py
@@ -16,11 +16,11 @@
 from rqalpha import cli
 
 __config__ = {
-    # A股最小手续费
+    # 股票最小手续费，单位元
     "cn_stock_min_commission": 5,
-    # 设置手续费乘数，默认为1
+    # 佣金倍率，即在默认的手续费率基础上按该倍数进行调整，股票的默认佣金为万八，期货默认佣金因合约而异
     "commission_multiplier": 1,
-    # 印花税乘数，默认为1
+    # 印花倍率，即在默认的印花税基础上按该倍数进行调整，股票默认印花税为千分之一，单边收取
     "tax_multiplier": 1,
 }
 
diff --git a/rqalpha/portfolio/position.py b/rqalpha/portfolio/position.py
index f09bcacad..bef0c86b4 100644
--- a/rqalpha/portfolio/position.py
+++ b/rqalpha/portfolio/position.py
@@ -91,9 +91,6 @@ def __init__(self, order_book_id, direction, init_quantity=0):
 
         self._direction_factor = 1 if direction == POSITION_DIRECTION.LONG else -1
 
-        event_bus = Environment.get_instance().event_bus
-        event_bus.prepend_listener(EVENT.PRE_BEFORE_TRADING, self._pre_before_trading)
-
     @property
     def order_book_id(self):
         # type: () -> str
@@ -209,12 +206,10 @@ def set_state(self, state):
         self._transaction_cost = state.get("transaction_cost", 0)
         self._prev_close = state.get("prev_close")
 
-    def _pre_before_trading(self, _):
-        self._prev_close = self.last_price
-
     def before_trading(self, trading_date):
         # type: (date) -> float
         # 返回该阶段导致总资金的变化量
+        self._prev_close = self.last_price
         self._transaction_cost = 0
         return 0
 
diff --git a/rqalpha/utils/__init__.py b/rqalpha/utils/__init__.py
index ca4df8601..dc297cd55 100644
--- a/rqalpha/utils/__init__.py
+++ b/rqalpha/utils/__init__.py
@@ -28,10 +28,7 @@
 import numpy as np
 
 from rqalpha.utils.exception import CustomError, CustomException
-from rqalpha.const import (
-    EXC_TYPE, INSTRUMENT_TYPE, DEFAULT_ACCOUNT_TYPE, UNDERLYING_SYMBOL_PATTERN, SIDE, POSITION_EFFECT,
-    POSITION_DIRECTION
-)
+from rqalpha.const import EXC_TYPE, INSTRUMENT_TYPE, DEFAULT_ACCOUNT_TYPE, SIDE, POSITION_EFFECT, POSITION_DIRECTION
 from rqalpha.utils.datetime_func import TimeRange
 from rqalpha.utils.i18n import gettext as _
 from rqalpha.utils.functools import lru_cache
@@ -151,12 +148,6 @@ def account_type_str2enum(type_str):
 ]
 
 
-def get_upper_underlying_symbol(order_book_id):
-    p = re.compile(UNDERLYING_SYMBOL_PATTERN)
-    result = p.findall(order_book_id)
-    return result[0] if len(result) == 1 else None
-
-
 def is_night_trading(universe):
     # for compatible
     from rqalpha.environment import Environment
diff --git a/rqalpha/utils/arg_checker.py b/rqalpha/utils/arg_checker.py
index adeb2715e..b96c094b9 100644
--- a/rqalpha/utils/arg_checker.py
+++ b/rqalpha/utils/arg_checker.py
@@ -332,7 +332,7 @@ def are_valid_query_entities(self):
         return self
 
     def _is_valid_frequency(self, func_name, value):
-        valid = isinstance(value, six.string_types) and value[-1] in ("d", "m")
+        valid = isinstance(value, six.string_types) and value[-1] in ("d", "m", "w")
         if valid:
             try:
                 valid = int(value[:-1]) > 0
@@ -342,7 +342,7 @@ def _is_valid_frequency(self, func_name, value):
         if not valid:
             raise RQInvalidArgument(
                 _(u"function {}: invalid {} argument, frequency should be in form of "
-                  u"'1m', '5m', '1d', got {} (type: {})").format(
+                  u"'1m', '5m', '1d', '1w' got {} (type: {})").format(
                     func_name, self.arg_name, value, type(value)
                 ))
 
diff --git a/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.mo b/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.mo
index b41b7e41b..6b73c1109 100644
Binary files a/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.mo and b/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.mo differ
diff --git a/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.po b/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.po
index 47093217c..de6270cde 100644
--- a/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.po
+++ b/rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2021-05-14 09:37+0800\n"
+"POT-Creation-Date: 2021-09-08 11:05+0800\n"
 "PO-Revision-Date: 2016-10-24 21:20+0800\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: zh_Hans_CN\n"
@@ -79,9 +79,9 @@ msgstr "期货指数连续合约[99] 在实盘模拟中暂不支持。"
 
 #: rqalpha/apis/api_base.py:159
 #: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:66
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:85
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:132
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:307
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:90
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:137
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:312
 msgid "Order Creation Failed: [{order_book_id}] No market data"
 msgstr "订单创建失败: 当前合约[{order_book_id}]没有市场数据。"
 
@@ -235,7 +235,7 @@ msgstr ""
 msgid "mod tear_down [END]   {}"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_accounts/position_model.py:290
+#: rqalpha/mod/rqalpha_mod_sys_accounts/position_model.py:294
 msgid "{order_book_id} is expired, close all positions by system"
 msgstr "{order_book_id} 已退市/交割，系统自动平仓"
 
@@ -253,20 +253,16 @@ msgid ""
 "{closable}"
 msgstr "订单创建失败：{order_book_id} 的仓位不足，目标平仓/行权量为 {quantity}，可平仓/行权量为 {closable}"
 
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:67
-msgid "order_book_id: {order_book_id} need stock account, please set and try again!"
-msgstr "标的 {order_book_id} 下单需要股票账户，请设置后重试！"
-
 #: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:49
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:98
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:153
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:103
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:158
 msgid "Order Creation Failed: 0 order quantity, order_book_id={order_book_id}"
 msgstr ""
 
 # msgid "Order Creation Failed: Order amount is 0."
 # msgstr "订单创建失败: 下单量为 0"
 #: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_future.py:54
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:81
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:86
 msgid "Limit order price should be positive"
 msgstr "Limit order 价格应该为正，请检查您的下单价格"
 
@@ -288,104 +284,178 @@ msgid ""
 "[{new_orders_repr}]"
 msgstr "订单被拆分，原订单：{original_order_repr}，新订单：[{new_orders_repr}]"
 
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:108
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:67
+msgid ""
+"order_book_id: {order_book_id} needs stock account, please set and try "
+"again!"
+msgstr ""
+
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:113
 msgid "insufficient cash, use all remaining cash({}) to create order"
 msgstr "现金不足，使用当前剩余资金({})创建订单"
 
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:292
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:297
 msgid "total percent should be lower than 1, current: {}"
 msgstr "目标持仓总占比需要小于 1，当前值：{}"
 
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:301
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:306
 msgid "target percent of {} should between 0 and 1, current: {}"
 msgstr "{} 的目标持仓需要在 0 到 1 之间，当前值：{}"
 
-#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:644
+#: rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py:649
 msgid "in get_dividend, start_date {} is later than the previous test day {}"
 msgstr "在 get_dividend 函数中，start_date {} 晚于当前回测时间的上一个交易日 {}。"
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:99
+#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:100
 msgid ""
 "config 'base.benchmark' is deprecated, use 'mod.sys_analyser.benchmark' "
 "instead"
 msgstr "配置'base.benchmark'已被弃用，使用'mod.sys_analyser.benchmark'代替"
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:181
+#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:182
 msgid "invalid init benchmark {}, should be in format 'order_book_id:weight'"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:186
+#: rqalpha/mod/rqalpha_mod_sys_analyser/mod.py:187
 msgid "invalid weight for instrument {order_book_id}: {weight}"
 msgstr ""
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:128
-msgid "Total Returns"
-msgstr "回测收益"
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:71
+msgid "TotalReturns"
+msgstr "策略收益率"
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:129
-msgid "Annual Returns"
-msgstr "回测年化收益"
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:72
+msgid "AnnualReturns"
+msgstr "策略年化收益率"
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:130
-msgid "Benchmark Returns"
-msgstr "基准收益"
-
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:132
-msgid "Benchmark Annual"
-msgstr "基准年化收益"
-
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:135
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:73
 msgid "Alpha"
 msgstr "阿尔法"
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:136
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:74
 msgid "Beta"
 msgstr "贝塔"
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:137
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:75
 msgid "Sharpe"
 msgstr "夏普率"
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:138
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:76
 msgid "Sortino"
-msgstr "所提诺比率"
+msgstr "索提诺比率"
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:139
-msgid "Information Ratio"
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:77
+msgid "InformationRatio"
 msgstr "信息比率"
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:141
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:79
+msgid "BenchmarkReturns"
+msgstr "基准收益率"
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:80
+msgid "BenchmarkAnnual"
+msgstr "基准年化收益率"
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:81
 msgid "Volatility"
 msgstr "波动率"
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:142
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:172
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:82
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:199
 msgid "MaxDrawdown"
 msgstr "最大回撤"
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:143
-msgid "Tracking Error"
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:83
+msgid "TrackingError"
 msgstr "跟踪误差"
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:144
-msgid "Downside Risk"
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:84
+msgid "DownsideRisk"
 msgstr "下行风险"
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:153
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:85
 msgid "MaxDD/MaxDDD"
-msgstr "最大回撤/最大回撤持续期"
+msgstr "最大回撤/最长回撤持续期"
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:87
+msgid "WeeklyAlpha"
+msgstr "周度阿尔法"
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:88
+msgid "WeeklyBeta"
+msgstr "周度贝塔"
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:89
+msgid "WeeklySharpe"
+msgstr "周度夏普率"
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:90
+msgid "WeeklySortino"
+msgstr "周度索提诺比率"
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:91
+msgid "WeeklyInfoRatio"
+msgstr "周度信息比率"
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:92
+msgid "WeeklyTrackingError"
+msgstr "周度跟踪误差"
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:93
+msgid "WeeklyMaxDrawdown"
+msgstr "周度最大回撤"
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:97
+msgid "ExcessReturns"
+msgstr "超额收益率"
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:98
+msgid "ExcessAnnual"
+msgstr "年化超额收益率"
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:99
+msgid "ExcessSharpe"
+msgstr "超额夏普率"
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:100
+msgid "ExcessVolatility"
+msgstr "超额收益波动率"
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:101
+msgid "ExcessAnnualVolatility"
+msgstr "超额收益年化波动率"
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:166
-msgid "strategy"
-msgstr "策略"
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:102
+msgid "ExcessMaxDD"
+msgstr "超额收益最大回撤"
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:168
-msgid "benchmark"
-msgstr "基准"
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:103
+msgid "ExcessMaxDD/ExcessMaxDDD"
+msgstr "超额收益最大回撤/最长回撤持续期"
 
-#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:175
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:189
+msgid "Strategy"
+msgstr "策略收益"
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:191
+msgid "Benchmark"
+msgstr "基准收益"
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:193
+msgid "Excess"
+msgstr "超额收益"
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:194
+msgid "Weekly"
+msgstr "策略周度收益"
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:196
+msgid "BenchmarkWeekly"
+msgstr "基准周度收益"
+
+#: rqalpha/mod/rqalpha_mod_sys_analyser/plot.py:200
 msgid "MaxDDD"
-msgstr "最大回撤持续期"
+msgstr "最长回撤持续期"
 
 #: rqalpha/mod/rqalpha_mod_sys_risk/validators/cash_validator.py:33
 msgid ""
@@ -423,42 +493,42 @@ msgid ""
 "trade: [{}...]"
 msgstr "订单创建失败，当前存在可能导致自成交的挂单：[{}...]"
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:115
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:116
 #: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:66
 msgid ""
 "Order Cancelled: current security [{order_book_id}] can not be traded in "
 "listed date [{listed_date}]"
 msgstr "订单被撤销: [{order_book_id}] 上市首日无法交易"
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:122
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:123
 #: rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py:72
 msgid "Order Cancelled: current bar [{order_book_id}] miss market data."
 msgstr "订单被拒单: [{order_book_id}] 当前缺失市场数据。"
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:147
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:148
 msgid "Order Cancelled: current bar [{order_book_id}] reach the limit_up price."
 msgstr "订单被拒单: [{order_book_id}] 已涨停。"
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:153
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:154
 msgid "Order Cancelled: current bar [{order_book_id}] reach the limit_down price."
 msgstr "订单被拒单: [{order_book_id}] 已跌停。"
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:160
-#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:166
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:161
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:167
 msgid "Order Cancelled: [{order_book_id}] has no liquidity."
 msgstr "合约 [{order_book_id}] 流动性不足，拒单。"
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:175
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:176
 msgid "Order Cancelled: {order_book_id} bar no volume"
 msgstr "订单被撤销：{order_book_id} 当前无成交量"
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:191
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:192
 msgid ""
 "Order Cancelled: market order {order_book_id} volume {order_volume} due "
 "to volume limit"
 msgstr "订单被撤销: 订单 [{order_book_id}] 的下单量 {order_volume} 超过了成交量限制。"
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:226
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:227
 msgid ""
 "Order Cancelled: market order {order_book_id} volume {order_volume} is "
 "larger than {volume_percent_limit} percent of current bar volume, fill "
@@ -467,28 +537,28 @@ msgstr ""
 "{order_book_id} 下单量 {order_volume} 超过当前 Bar 成交量的 "
 "{volume_percent_limit}%，实际成交 {filled_volume}"
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:226
+#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:321
 msgid ""
-"Order Cancelled: market order {order_book_id} fill {filled_volume} actually"
-msgstr ""
-"订单取消：标的 {order_book_id} 实际成交 {filled_volume}"
+"Order Cancelled: market order {order_book_id} fill {filled_volume} "
+"actually"
+msgstr "订单取消：标的 {order_book_id} 实际成交 {filled_volume}"
 
 #: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:42
 msgid "invalid margin multiplier value: value range is (0, +∞]"
 msgstr "无效的 保证金乘数 设置: 其值范围为 (0, +∞]"
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:51
-#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:58
+#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:52
+#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:59
 msgid "Not supported matching type {}"
 msgstr "不支持撮合类型: {}"
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:62
+#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:63
 msgid ""
 "matching_type = 'next_bar' is abandoned when frequency == '1d',Current "
 "matching_type is 'current_bar'."
 msgstr "日回测 'next_bar' 撮合方式已废弃，当前按 'current_bar' 撮合方式成交"
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:105
+#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:108
 msgid "NO account_type = ({}) in {}"
 msgstr ""
 
@@ -504,32 +574,27 @@ msgstr "您在涨停状态下交易了 {quantity} 单位的 {order_book_id}"
 msgid "You have traded {order_book_id} with {quantity} lots in limit down status"
 msgstr "您在跌停状态下交易了 {quantity} 单位的 {order_book_id}"
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:99
+#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:104
 msgid "unsupported position_effect {}"
 msgstr "不支持的 position_effect {}"
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:120
+#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:125
 msgid "{order_id} order has been cancelled by user."
 msgstr "订单 {order_id} 被手动取消。"
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:136
+#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:141
 msgid "Order Rejected: {order_book_id} can not match. Market close."
 msgstr "订单被拒单: {order_book_id} 当天交易结束，订单无法成交。"
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:177
+#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:178
 msgid "{order_book_id} should be subscribed when frequency is tick."
 msgstr "tick回测下单失败，请使用 subscribe 订阅合约 {order_book_id}。"
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_event_source.py:44
-#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_event_source.py:72
-msgid "Unsupported market {}"
-msgstr "不支持的市场类型 {}"
-
-#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_event_source.py:52
+#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_event_source.py:46
 msgid "Current universe is empty. Please use subscribe function before trade"
 msgstr "当前合约池为空。请使用 subscribe 订阅合约。"
 
-#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_event_source.py:210
+#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_event_source.py:196
 msgid "Frequency {} is not support."
 msgstr "不支持回测频率: {}"
 
@@ -555,7 +620,7 @@ msgid ""
 " +∞)"
 msgstr ""
 
-#: rqalpha/model/bar.py:374
+#: rqalpha/model/bar.py:371
 msgid "id_or_symbols {} does not exist"
 msgstr "您选择的证券[{}]不存在。"
 
@@ -574,15 +639,15 @@ msgstr "不合法的账户类型 {}， 请在以下账户类型中选择 {}"
 msgid "Cash add {}. units {} become to {}"
 msgstr "入金{}元，份额由{}变动为{}"
 
-#: rqalpha/portfolio/account.py:304
+#: rqalpha/portfolio/account.py:309
 msgid "Trigger Forced Liquidation, current total_value is 0"
 msgstr "触发强制清算，当前总权益为 0"
 
-#: rqalpha/portfolio/account.py:425
+#: rqalpha/portfolio/account.py:431
 msgid "insufficient cash, current {}, target withdrawal {}"
 msgstr "现金不足，当前现金 {}，目标出金 {}"
 
-#: rqalpha/portfolio/position.py:175
+#: rqalpha/portfolio/position.py:169
 msgid "last price of position {} is not supposed to be nan"
 msgstr "仓位 {} 最新价不应该为 nan"
 
@@ -688,40 +753,36 @@ msgstr "\"{}\" 已被弃用，请使用 \"{}\"，查询文档以获取更多信
 msgid "in parse_user_config, exception: {e}"
 msgstr "在执行 parse_user_config 时，产生异常: {e}"
 
-#: rqalpha/utils/config.py:207
+#: rqalpha/utils/config.py:206
 msgid "Invalid future info: underlying_symbol {} is illegal."
 msgstr "无效的 future info: 合约品种 {} 不合法"
 
-#: rqalpha/utils/config.py:222
+#: rqalpha/utils/config.py:221
 msgid ""
 "Invalid future info: commission_type is suppose to be BY_MONEY or "
 "BY_VOLUME"
 msgstr "无效的 future info: commission_type 字段应为 BY_MONEY 或 BY_VOLUME"
 
-#: rqalpha/utils/config.py:226
+#: rqalpha/utils/config.py:225
 msgid "Invalid future info: field {} is not valid"
 msgstr "无效的 future info: 字段 {} 不合法"
 
-#: rqalpha/utils/config.py:256
+#: rqalpha/utils/config.py:255
 msgid "invalid init position {}, should be in format 'order_book_id:quantity'"
 msgstr "无效的初始仓位：{}，格式应该为 'order_book_id:quantity'"
 
-#: rqalpha/utils/config.py:261
+#: rqalpha/utils/config.py:260
 msgid "invalid quantity for instrument {order_book_id}: {quantity}"
 msgstr "初始仓位中合约 {order_book_id} 的数量 {quantity} 不是有效的数字"
 
-#: rqalpha/utils/config.py:276
+#: rqalpha/utils/config.py:275
 msgid "unknown run type: {}"
 msgstr "未知的策略类型：{}"
 
-#: rqalpha/utils/config.py:289
+#: rqalpha/utils/config.py:288
 msgid "unknown persist mode: {}"
 msgstr "未知的持久化模式：{}"
 
-#: rqalpha/utils/config.py:302
-msgid "unknown market type: {}"
-msgstr "未知的市场类型：{}"
-
 #: rqalpha/utils/functools.py:50
 msgid ""
 "function {}: invalid {} argument, expected an order_book_id or instrument"
@@ -1158,3 +1219,41 @@ msgstr "不支持调用函数 {}，请检查 Account 设置或 mod 配置"
 #~ msgid "invalid commission multiplier value: value range is [0, +∞)"
 #~ msgstr "无效的 手续费乘数 设置: 其值范围为 [0, +∞)"
 
+#~ msgid ""
+#~ "order_book_id: {order_book_id} need stock "
+#~ "account, please set and try again!"
+#~ msgstr "标的 {order_book_id} 下单需要股票账户，请设置后重试！"
+
+#~ msgid "Total Returns"
+#~ msgstr "回测收益"
+
+#~ msgid "Annual Returns"
+#~ msgstr "回测年化收益"
+
+#~ msgid "Benchmark Returns"
+#~ msgstr "基准收益"
+
+#~ msgid "Benchmark Annual"
+#~ msgstr "基准年化收益"
+
+#~ msgid "Information Ratio"
+#~ msgstr "信息比率"
+
+#~ msgid "Tracking Error"
+#~ msgstr "跟踪误差"
+
+#~ msgid "Downside Risk"
+#~ msgstr "下行风险"
+
+#~ msgid "strategy"
+#~ msgstr "策略"
+
+#~ msgid "benchmark"
+#~ msgstr "基准"
+
+#~ msgid "Unsupported market {}"
+#~ msgstr "不支持的市场类型 {}"
+
+#~ msgid "unknown market type: {}"
+#~ msgstr "未知的市场类型：{}"
+
diff --git a/setup.cfg b/setup.cfg
index e3c995785..e21387c1a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -5,7 +5,7 @@
 
 [metadata]
 name = rqalpha
-version = 4.5.2
+version = 4.6.0
 
 [versioneer]
 VCS = git
diff --git a/setup.py b/setup.py
index d8994c502..f68069550 100644
--- a/setup.py
+++ b/setup.py
@@ -27,7 +27,7 @@
     'simplejson',
     'PyYAML',
     'tabulate',
-    'rqrisk',
+    'rqrisk >=0.0.14',
     'h5py',
     'matplotlib >=2.2.0',
 ]


Test patch:
diff --git a/test.py b/test.py
index 1ec5f8aaf..e976b2f2e 100644
--- a/test.py
+++ b/test.py
@@ -18,16 +18,15 @@
 import os
 import csv
 from datetime import datetime
-from typing import Optional, Tuple
 
 from six import iteritems
 import pandas as pd
+from pandas.testing import assert_frame_equal
 import numpy as np
 import coverage
 
 from rqalpha import run, run_func
 from rqalpha.utils.config import set_locale
-from rqalpha.utils.logger import system_log
 
 TEST_DIR = os.path.abspath("./tests/")
 TEST_OUT = os.path.abspath("./tests/outs/")
@@ -43,32 +42,22 @@ def run_tests(file_path=None):
     )}
     error_map = {}
     for name, filename in tests.items():
-        try:
-            result_data = run_test(filename)
-            if result_data is not None:
-                error_map[name] = result_data
-        except Exception as e:
-            error_map[name] = e
-    for filename, result_data in iteritems(error_map):
+        err = run_test(filename)
+        if err is not None:
+            error_map[name] = err
+    for filename, err in iteritems(error_map):
         print(u"*" * 20, u"[{}]did not pass!".format(filename), u"*" * 20)
-        if isinstance(result_data, Exception):
-            system_log.error(result_data)
-        else:
-            _, __, result = result_data
-            print(result.all())
+        print(err)
     print(u"=" * 40)
     print("[{}|{}] strategies has been passed!".format(len(tests) - len(error_map), len(tests)))
     return len(error_map)
 
 
 def run_test(filename):
-    # type: (str) -> Optional[Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]]
     config = {"base": {"strategy_file": os.path.join(TEST_DIR, filename)}}
     print(u"Start test: " + str(config["base"]["strategy_file"]))
     result_dict = run(config)["sys_analyser"]
 
-    # del df['positions']
-
     old_pickle_file = os.path.join(TEST_OUT, filename.replace(".py", ".pkl"))
 
     if not os.path.exists(old_pickle_file):
@@ -90,7 +79,7 @@ def run_test(filename):
 
         result = df.eq(old_df)
         if not result.all().all():
-            return df, old_df, result
+            return result.all()
 
         # 比较 summary
         old_df = (
@@ -122,9 +111,10 @@ def run_test(filename):
             del df["strategy_file"]
         except:
             pass
-        result = df.eq(old_df)
-        if not result.all().all():
-            return old_result_dict, result_dict, result
+        try:
+            assert_frame_equal(df, old_df, check_less_precise=8)
+        except AssertionError as e:
+            return str(e)
 
 
 def is_enable_coverage():
diff --git a/tests/outs/test_f_buy_and_hold.pkl b/tests/outs/test_f_buy_and_hold.pkl
index 5fc8d5a0d..3165470b8 100644
Binary files a/tests/outs/test_f_buy_and_hold.pkl and b/tests/outs/test_f_buy_and_hold.pkl differ
diff --git a/tests/outs/test_f_macd.pkl b/tests/outs/test_f_macd.pkl
index 4032d193a..ef743d332 100644
Binary files a/tests/outs/test_f_macd.pkl and b/tests/outs/test_f_macd.pkl differ
diff --git a/tests/outs/test_f_macd_signal.pkl b/tests/outs/test_f_macd_signal.pkl
index f648293b9..7c5aab626 100644
Binary files a/tests/outs/test_f_macd_signal.pkl and b/tests/outs/test_f_macd_signal.pkl differ
diff --git a/tests/outs/test_f_mean_reverting.pkl b/tests/outs/test_f_mean_reverting.pkl
index c09e97003..6c0c87539 100644
Binary files a/tests/outs/test_f_mean_reverting.pkl and b/tests/outs/test_f_mean_reverting.pkl differ
diff --git a/tests/outs/test_s_buy_and_hold.pkl b/tests/outs/test_s_buy_and_hold.pkl
index f5bcc88af..848ea7203 100644
Binary files a/tests/outs/test_s_buy_and_hold.pkl and b/tests/outs/test_s_buy_and_hold.pkl differ
diff --git a/tests/outs/test_s_dual_thrust.pkl b/tests/outs/test_s_dual_thrust.pkl
index 722127690..f6bb3fe06 100644
Binary files a/tests/outs/test_s_dual_thrust.pkl and b/tests/outs/test_s_dual_thrust.pkl differ
diff --git a/tests/outs/test_s_scheduler.pkl b/tests/outs/test_s_scheduler.pkl
index 03e3cd5b8..cb443c1ee 100644
Binary files a/tests/outs/test_s_scheduler.pkl and b/tests/outs/test_s_scheduler.pkl differ
diff --git a/tests/outs/test_s_tick_size.pkl b/tests/outs/test_s_tick_size.pkl
index 772b35a00..f77b77382 100644
Binary files a/tests/outs/test_s_tick_size.pkl and b/tests/outs/test_s_tick_size.pkl differ
diff --git a/tests/outs/test_s_turtle.pkl b/tests/outs/test_s_turtle.pkl
index 926f5baaf..b5d2d20e4 100644
Binary files a/tests/outs/test_s_turtle.pkl and b/tests/outs/test_s_turtle.pkl differ
diff --git a/tests/outs/test_s_turtle_signal.pkl b/tests/outs/test_s_turtle_signal.pkl
index c77d4dacf..f3872f4fb 100644
Binary files a/tests/outs/test_s_turtle_signal.pkl and b/tests/outs/test_s_turtle_signal.pkl differ
diff --git a/tests/outs/test_sf_buy_and_hold.pkl b/tests/outs/test_sf_buy_and_hold.pkl
index dfe8127f1..9360bda66 100644
Binary files a/tests/outs/test_sf_buy_and_hold.pkl and b/tests/outs/test_sf_buy_and_hold.pkl differ