From d4967c711e5a5e591030321f44ab47c482574f39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E5=98=89=E4=BF=8A?= <654181984@qq.com> Date: Tue, 25 Jul 2023 16:36:24 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E4=BB=85=E5=9C=A8=E4=BA=A4=E6=98=93?= =?UTF-8?q?=E6=97=B6=E6=AE=B5=E5=86=85=E6=92=AE=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../signal_broker.py | 20 ++++++++++++++++--- .../simulation_broker.py | 12 +++++++++-- rqalpha/model/instrument.py | 7 +++++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py b/rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py index 832c75f37..e4a98faf7 100644 --- a/rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py +++ b/rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py @@ -24,10 +24,11 @@ from rqalpha.utils.logger import user_system_log from rqalpha.utils.i18n import gettext as _ from rqalpha.utils import is_valid_price +from rqalpha.core.execution_context import ExecutionContext from rqalpha.core.events import EVENT, Event from rqalpha.model.trade import Trade from rqalpha.model.order import ALGO_ORDER_STYLES -from rqalpha.const import SIDE, ORDER_TYPE, POSITION_EFFECT +from rqalpha.const import SIDE, ORDER_TYPE, POSITION_EFFECT, EXECUTION_PHASE from .slippage import SlippageDecider @@ -37,9 +38,11 @@ def __init__(self, env, mod_config): self._env = env self._slippage_decider = SlippageDecider(mod_config.slippage_model, mod_config.slippage) self._price_limit = mod_config.price_limit + self._open_orders = [] + self._env.event_bus.add_listener(EVENT.BAR, self.on_bar) def get_open_orders(self, order_book_id=None): - return [] + return [order for _, order in self._open_orders] def submit_order(self, order): if order.position_effect == POSITION_EFFECT.EXERCISE: @@ -50,12 +53,23 @@ def submit_order(self, order): return order.active() self._env.event_bus.publish_event(Event(EVENT.ORDER_CREATION_PASS, account=account, order=order)) - self._match(account, order) + instrument = self._env.data_proxy.instrument(order.order_book_id) + if ExecutionContext.phase() == EXECUTION_PHASE.ON_BAR and not instrument.during_continuous_auction(self._env.calendar_dt): + self._open_orders.append((account, order)) + else: + self._match(account, order) def cancel_order(self, order): user_system_log.warn(_(u"cancel_order function is not supported in signal mode")) return None + def on_bar(self, event): + for account, order in self._open_orders: + instrument = self._env.data_proxy.instrument(order.order_book_id) + if not order.is_final() and instrument.during_continuous_auction(self._env.calendar_dt): + self._match(account, order) + self._open_orders = [(a, o) for a, o in self._open_orders if not o.is_final()] + def _match(self, account, order): order_book_id = order.order_book_id price_board = self._env.price_board diff --git a/rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py b/rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py index 48ae65334..55c5e784b 100644 --- a/rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py +++ b/rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py @@ -166,8 +166,16 @@ def on_tick(self, event): self._match(tick.order_book_id) def _match(self, order_book_id=None): - order_filter = lambda a_and_o: not (a_and_o[1].is_final() or (order_book_id and a_and_o[1].order_book_id != order_book_id)) - for account, order in filter(order_filter, self._open_orders): + order_filter = lambda a_and_o: all([ + not a_and_o[1].is_final(), # 订单未完成 + True if order_book_id is None else a_and_o[1].order_book_id == order_book_id, # 只撮合指定的订单 + ]) + open_order_filter = lambda a_and_o: all([ + order_filter(a_and_o), + # 得在交易时段内 + self._env.data_proxy.instrument(a_and_o[1].order_book_id).during_continuous_auction(self._env.calendar_dt) + ]) + for account, order in filter(open_order_filter, self._open_orders): self._get_matcher(order.order_book_id).match(account, order, open_auction=False) for account, order in filter(order_filter, self._open_auction_orders): self._get_matcher(order.order_book_id).match(account, order, open_auction=True) diff --git a/rqalpha/model/instrument.py b/rqalpha/model/instrument.py index 2302d7133..6433aec51 100644 --- a/rqalpha/model/instrument.py +++ b/rqalpha/model/instrument.py @@ -375,6 +375,13 @@ def trading_hours(self): trading_period.append(TimeRange(start, end)) return trading_period + def during_continuous_auction(self, dt): + """ 是否处于连续竞价时间段内 """ + for time_range in self.trading_hours: + if time_range.start <= dt.time() <= time_range.end: + return True + return False + @property def trading_code(self): # type: () -> str From 04f279272b96f32a953395d6fef558c9859cdfa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E5=98=89=E4=BF=8A?= <654181984@qq.com> Date: Wed, 26 Jul 2023 17:15:38 +0800 Subject: [PATCH 2/3] fix; add test --- .../signal_broker.py | 20 +----- .../simulation_broker.py | 23 +++---- rqalpha/model/instrument.py | 5 +- .../mod/sys_simulation/test_match.py | 66 +++++++++++++++++++ 4 files changed, 81 insertions(+), 33 deletions(-) create mode 100644 tests/api_tests/mod/sys_simulation/test_match.py diff --git a/rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py b/rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py index e4a98faf7..832c75f37 100644 --- a/rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py +++ b/rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py @@ -24,11 +24,10 @@ from rqalpha.utils.logger import user_system_log from rqalpha.utils.i18n import gettext as _ from rqalpha.utils import is_valid_price -from rqalpha.core.execution_context import ExecutionContext from rqalpha.core.events import EVENT, Event from rqalpha.model.trade import Trade from rqalpha.model.order import ALGO_ORDER_STYLES -from rqalpha.const import SIDE, ORDER_TYPE, POSITION_EFFECT, EXECUTION_PHASE +from rqalpha.const import SIDE, ORDER_TYPE, POSITION_EFFECT from .slippage import SlippageDecider @@ -38,11 +37,9 @@ def __init__(self, env, mod_config): self._env = env self._slippage_decider = SlippageDecider(mod_config.slippage_model, mod_config.slippage) self._price_limit = mod_config.price_limit - self._open_orders = [] - self._env.event_bus.add_listener(EVENT.BAR, self.on_bar) def get_open_orders(self, order_book_id=None): - return [order for _, order in self._open_orders] + return [] def submit_order(self, order): if order.position_effect == POSITION_EFFECT.EXERCISE: @@ -53,23 +50,12 @@ def submit_order(self, order): return order.active() self._env.event_bus.publish_event(Event(EVENT.ORDER_CREATION_PASS, account=account, order=order)) - instrument = self._env.data_proxy.instrument(order.order_book_id) - if ExecutionContext.phase() == EXECUTION_PHASE.ON_BAR and not instrument.during_continuous_auction(self._env.calendar_dt): - self._open_orders.append((account, order)) - else: - self._match(account, order) + self._match(account, order) def cancel_order(self, order): user_system_log.warn(_(u"cancel_order function is not supported in signal mode")) return None - def on_bar(self, event): - for account, order in self._open_orders: - instrument = self._env.data_proxy.instrument(order.order_book_id) - if not order.is_final() and instrument.during_continuous_auction(self._env.calendar_dt): - self._match(account, order) - self._open_orders = [(a, o) for a, o in self._open_orders if not o.is_final()] - def _match(self, account, order): order_book_id = order.order_book_id price_board = self._env.price_board diff --git a/rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py b/rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py index 55c5e784b..2ae659dd5 100644 --- a/rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py +++ b/rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py @@ -119,7 +119,7 @@ def submit_order(self, order): order.active() self._env.event_bus.publish_event(Event(EVENT.ORDER_CREATION_PASS, account=account, order=order)) if self._match_immediately: - self._match() + self._match(self._env.calendar_dt) def cancel_order(self, order): account = self._env.get_account(order.order_book_id) @@ -158,23 +158,18 @@ def pre_settlement(self, __): def on_bar(self, event): for matcher in self._matchers.values(): matcher.update(event) - self._match() + self._match(event.calendar_dt) def on_tick(self, event): tick = event.tick self._get_matcher(tick.order_book_id).update(event) - self._match(tick.order_book_id) - - def _match(self, order_book_id=None): - order_filter = lambda a_and_o: all([ - not a_and_o[1].is_final(), # 订单未完成 - True if order_book_id is None else a_and_o[1].order_book_id == order_book_id, # 只撮合指定的订单 - ]) - open_order_filter = lambda a_and_o: all([ - order_filter(a_and_o), - # 得在交易时段内 - self._env.data_proxy.instrument(a_and_o[1].order_book_id).during_continuous_auction(self._env.calendar_dt) - ]) + self._match(event.calendar_dt, tick.order_book_id) + + def _match(self, dt, order_book_id=None): + # 撮合未完成的订单,若指定标的时只撮合指定的标的的订单 + order_filter = lambda a_and_o: (not a_and_o[1].is_final()) and (True if order_book_id is None else a_and_o[1].order_book_id == order_book_id) + # + 需要在交易时段内 + open_order_filter = lambda a_and_o: order_filter(a_and_o) and self._env.data_proxy.instrument(a_and_o[1].order_book_id).during_continuous_auction(dt.time()) for account, order in filter(open_order_filter, self._open_orders): self._get_matcher(order.order_book_id).match(account, order, open_auction=False) for account, order in filter(order_filter, self._open_auction_orders): diff --git a/rqalpha/model/instrument.py b/rqalpha/model/instrument.py index 6433aec51..eb08e43e2 100644 --- a/rqalpha/model/instrument.py +++ b/rqalpha/model/instrument.py @@ -375,10 +375,11 @@ def trading_hours(self): trading_period.append(TimeRange(start, end)) return trading_period - def during_continuous_auction(self, dt): + def during_continuous_auction(self, time): + # type: (datetime.time) -> bool """ 是否处于连续竞价时间段内 """ for time_range in self.trading_hours: - if time_range.start <= dt.time() <= time_range.end: + if time_range.start <= time <= time_range.end: return True return False diff --git a/tests/api_tests/mod/sys_simulation/test_match.py b/tests/api_tests/mod/sys_simulation/test_match.py new file mode 100644 index 000000000..7395850f2 --- /dev/null +++ b/tests/api_tests/mod/sys_simulation/test_match.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# 版权所有 2019 深圳米筐科技有限公司(下称“米筐科技”) +# +# 除非遵守当前许可,否则不得使用本软件。 +# +# * 非商业用途(非商业用途指个人出于非商业目的使用本软件,或者高校、研究所等非营利机构出于教育、科研等目的使用本软件): +# 遵守 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 + +__config__ = { + "base": { + "frequency": "1d", + "accounts": { + "future": 1000000, + } + } +} + + +def test_match(): + + try: + import rqalpha_mod_ricequant_data + except Exception: + print("github上无数据,跳过测试,仅在本地测试") + return {} + + __config__ = { + "base": { + "data_bundle_path": os.path.expanduser("~/.rqalpha-plus/bundle"), + "start_date": "2023-07-07", + "end_date": "2023-07-07", + "frequency": "1m", + "accounts": { + "future": 1000000, + } + }, + "mod": { + "ricequant_data": { + "enabled": True + } + } + } + + def init(context): + context.order = None + subscribe("ZN2309") + subscribe("RR2309") + + def handle_bar(context, bar_dict): + dt_str = context.now.strftime("%Y%m%d%H%M%S") + if dt_str == "20230706210100": + context.order = buy_open("RR2309", 1, price_or_style=3500) + if dt_str == "20230706230200": + assert "订单被拒单: [RR2309] 当前缺失市场数据。" != context.order._message, "不能在非交易时段尝试撮合" + + return locals() From 69bbaf241d7aedef43e0303b3bc18f06c149914f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E5=98=89=E4=BF=8A?= <654181984@qq.com> Date: Wed, 26 Jul 2023 18:04:51 +0800 Subject: [PATCH 3/3] fix --- tests/api_tests/mod/sys_simulation/test_match.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/api_tests/mod/sys_simulation/test_match.py b/tests/api_tests/mod/sys_simulation/test_match.py index 7395850f2..631a61126 100644 --- a/tests/api_tests/mod/sys_simulation/test_match.py +++ b/tests/api_tests/mod/sys_simulation/test_match.py @@ -16,6 +16,8 @@ # 详细的授权流程,请联系 public@ricequant.com 获取。 import os +from rqalpha.const import ORDER_STATUS + __config__ = { "base": { "frequency": "1d", @@ -38,7 +40,7 @@ def test_match(): "base": { "data_bundle_path": os.path.expanduser("~/.rqalpha-plus/bundle"), "start_date": "2023-07-07", - "end_date": "2023-07-07", + "end_date": "2023-07-10", "frequency": "1m", "accounts": { "future": 1000000, @@ -58,9 +60,17 @@ def init(context): def handle_bar(context, bar_dict): dt_str = context.now.strftime("%Y%m%d%H%M%S") + # 当天夜盘 if dt_str == "20230706210100": context.order = buy_open("RR2309", 1, price_or_style=3500) - if dt_str == "20230706230200": + elif dt_str == "20230706230200": + assert context.order.status == ORDER_STATUS.ACTIVE, "盘中休息也保持active" assert "订单被拒单: [RR2309] 当前缺失市场数据。" != context.order._message, "不能在非交易时段尝试撮合" + # 第二天早盘 + elif dt_str == "20230707091000": + assert context.order.status == ORDER_STATUS.ACTIVE, "夜盘下的order在未成交、撤单等情况下应保持active状态" + # 第二天夜盘 + elif dt_str == "20230707210100": + assert context.order.status == ORDER_STATUS.REJECTED, "上个交易日的订单在当天收盘后未被拒单" return locals()