def submit_order(self, order): account = self._env.get_account(order.order_book_id) self._env.event_bus.publish_event( Event(EVENT.ORDER_PENDING_NEW, account=account, order=copy(order))) if order.is_final(): return order.active() self._env.event_bus.publish_event( Event(EVENT.ORDER_CREATION_PASS, account=account, order=copy(order))) self._match(account, order)
def submit_order(self, order): account = self._env.get_account(order.order_book_id) self._env.event_bus.publish_event( Event(EVENT.ORDER_PENDING_NEW, account=account, order=order)) if order.is_final(): return if self._env.config.base.frequency == '1d' and not self._match_immediately: self._delayed_orders.append((account, order)) return self._open_orders.append((account, order)) order.active() self._env.event_bus.publish_event( Event(EVENT.ORDER_CREATION_PASS, account=account, order=order)) if self._match_immediately: self._match()
def update(self, universe): if isinstance(universe, (six.string_types, Instrument)): universe = [universe] new_set = set(universe) if new_set != self._set: self._set = new_set Environment.get_instance().event_bus.publish_event( Event(EVENT.POST_UNIVERSE_CHANGED, universe=self._set))
def output_profile_result(env): stdout_trap = six.StringIO() env.profile_deco.print_stats(stdout_trap) profile_output = stdout_trap.getvalue() profile_output = profile_output.rstrip() six.print_(profile_output) env.event_bus.publish_event( Event(EVENT.ON_LINE_PROFILER_RESULT, result=profile_output))
def init(self): if not self._init: return with ExecutionContext(EXECUTION_PHASE.ON_INIT): with ModifyExceptionFromType(EXC_TYPE.USER_EXC): self._init(self._user_context) Environment.get_instance().event_bus.publish_event(Event(EVENT.POST_USER_INIT))
def cancel_order(self, order): account = self._env.get_account(order.order_book_id) self._env.event_bus.publish_event( Event(EVENT.ORDER_PENDING_CANCEL, account=account, order=order)) order.mark_cancelled( _(u"{order_id} order has been cancelled by user.").format( order_id=order.order_id)) self._env.event_bus.publish_event( Event(EVENT.ORDER_CANCELLATION_PASS, account=account, order=order)) try: self._open_orders.remove((account, order)) except ValueError: try: self._delayed_orders.remove((account, order)) except ValueError: pass
def _clear_de_listed(self, event): de_listed = set() env = Environment.get_instance() for o in self._set: i = env.data_proxy.instruments(o) if i.de_listed_date <= env.trading_dt: de_listed.add(o) if de_listed: self._set -= de_listed env.event_bus.publish_event( Event(EVENT.POST_UNIVERSE_CHANGED, universe=self._set))
def after_trading(self, event): for account, order in self._open_orders: order.mark_rejected( _(u"Order Rejected: {order_book_id} can not match. Market close." ).format(order_book_id=order.order_book_id)) self._env.event_bus.publish_event( Event(EVENT.ORDER_UNSOLICITED_UPDATE, account=account, order=order)) self._open_orders = self._delayed_orders self._delayed_orders = []
def _match(self, order_book_id=None): open_orders = self._open_orders if order_book_id is not None: open_orders = [(a, o) for (a, o) in self._open_orders if o.order_book_id == order_book_id] self._matcher.match(open_orders) final_orders = [(a, o) for a, o in self._open_orders if o.is_final()] self._open_orders = [(a, o) for a, o in self._open_orders if not o.is_final()] for account, order in final_orders: if order.status == ORDER_STATUS.REJECTED or order.status == ORDER_STATUS.CANCELLED: self._env.event_bus.publish_event( Event(EVENT.ORDER_UNSOLICITED_UPDATE, account=account, order=order))
def test_on_tick(self): from rqrobot.events import EVENT, Event self.assertEqual(self.benchmark_account.total_value, self.benchmark_account_total_cash) self.assertEqual(len(self.benchmark_account.positions), 0) mock_event = Event(EVENT.TICK, tick=mock_tick(mock_instrument(self.benchmark), last=3000)) self.env.event_bus.publish_event(mock_event) self.assertAlmostEqual( self.benchmark_account.positions[self.benchmark].quantity, 4000 / 3000) self.env.event_bus.publish_event(mock_event) self.assertAlmostEqual( self.benchmark_account.positions[self.benchmark].quantity, 4000 / 3000)
def check_before_trading(e): if self._last_before_trading == event.trading_dt.date(): return False if self._env.config.extra.is_hold: return False if self._last_before_trading: # don't publish settlement on first day publish_settlement() self._last_before_trading = e.trading_dt.date() update_time(e) event_bus.publish_event(PRE_BEFORE_TRADING) event_bus.publish_event( Event(EVENT.BEFORE_TRADING, calendar_dt=e.calendar_dt, trading_dt=e.trading_dt)) event_bus.publish_event(POST_BEFORE_TRADING) return True
def test_on_bar(self): from rqrobot.events import EVENT, Event self.assertEqual(self.benchmark_account.total_value, self.benchmark_account_total_cash) self.assertEqual(len(self.benchmark_account.positions), 0) mock_event = Event(EVENT.PRE_BAR, bar_dict={ self.benchmark: mock_bar(mock_instrument(self.benchmark), close=3000) }) self.env.event_bus.publish_event(mock_event) self.assertAlmostEqual( self.benchmark_account.positions[self.benchmark].quantity, 4000 / 3000) self.env.event_bus.publish_event(mock_event) self.assertAlmostEqual( self.benchmark_account.positions[self.benchmark].quantity, 4000 / 3000)
def match(self, open_orders): price_board = self._env.price_board for account, order in open_orders: order_book_id = order.order_book_id instrument = self._env.get_instrument(order_book_id) deal_price = self._deal_price_decider(order_book_id, order.side) if not is_valid_price(deal_price): listed_date = instrument.listed_date.date() if listed_date == self._trading_dt.date(): reason = _( u"Order Cancelled: current security [{order_book_id}] can not be traded in listed date [{listed_date}]" ).format( order_book_id=order.order_book_id, listed_date=listed_date, ) else: reason = _( u"Order Cancelled: current bar [{order_book_id}] miss market data." ).format(order_book_id=order.order_book_id) order.mark_rejected(reason) continue if order.type == ORDER_TYPE.LIMIT: if order.side == SIDE.BUY and order.price < deal_price: continue if order.side == SIDE.SELL and order.price > deal_price: continue # 是否限制涨跌停不成交 if self._price_limit: if order.side == SIDE.BUY and deal_price >= price_board.get_limit_up( order_book_id): continue if order.side == SIDE.SELL and deal_price <= price_board.get_limit_down( order_book_id): continue if self._liquidity_limit: if order.side == SIDE.BUY and price_board.get_a1( order_book_id) == 0: continue if order.side == SIDE.SELL and price_board.get_b1( order_book_id) == 0: continue else: if self._price_limit: if order.side == SIDE.BUY and deal_price >= price_board.get_limit_up( order_book_id): reason = _( "Order Cancelled: current bar [{order_book_id}] reach the limit_up price." ).format(order_book_id=order.order_book_id) order.mark_rejected(reason) continue if order.side == SIDE.SELL and deal_price <= price_board.get_limit_down( order_book_id): reason = _( "Order Cancelled: current bar [{order_book_id}] reach the limit_down price." ).format(order_book_id=order.order_book_id) order.mark_rejected(reason) continue if self._liquidity_limit: if order.side == SIDE.BUY and price_board.get_a1( order_book_id) == 0: reason = _( "Order Cancelled: [{order_book_id}] has no liquidity." ).format(order_book_id=order.order_book_id) order.mark_rejected(reason) continue if order.side == SIDE.SELL and price_board.get_b1( order_book_id) == 0: reason = _( "Order Cancelled: [{order_book_id}] has no liquidity." ).format(order_book_id=order.order_book_id) order.mark_rejected(reason) continue if self._volume_limit: bar = self._env.bar_dict[order_book_id] volume_limit = round( bar.volume * self._volume_percent) - self._turnover[order.order_book_id] round_lot = instrument.round_lot volume_limit = (volume_limit // round_lot) * round_lot if volume_limit <= 0: if order.type == ORDER_TYPE.MARKET: reason = _( u"Order Cancelled: market order {order_book_id} volume {order_volume}" u" due to volume limit").format( order_book_id=order.order_book_id, order_volume=order.quantity) order.mark_cancelled(reason) continue unfilled = order.unfilled_quantity fill = min(unfilled, volume_limit) else: fill = order.unfilled_quantity ct_amount = account.positions.get_or_create( order.order_book_id).cal_close_today_amount(fill, order.side) price = self._slippage_decider.get_trade_price(order, deal_price) trade = Trade.__from_create__( order_id=order.order_id, price=price, amount=fill, side=order.side, position_effect=order.position_effect, order_book_id=order.order_book_id, frozen_price=order.frozen_price, close_today_amount=ct_amount) trade._commission = self._env.get_trade_commission( account_type_str2enum(account.type), trade) trade._tax = self._env.get_trade_tax( account_type_str2enum(account.type), trade) order.fill(trade) self._turnover[order.order_book_id] += fill self._env.event_bus.publish_event( Event(EVENT.TRADE, account=account, trade=trade, order=order)) if order.type == ORDER_TYPE.MARKET and order.unfilled_quantity != 0: reason = _( u"Order Cancelled: market order {order_book_id} volume {order_volume} is" u" larger than {volume_percent_limit} percent of current bar volume, fill {filled_volume} actually" ).format(order_book_id=order.order_book_id, order_volume=order.quantity, filled_volume=order.filled_quantity, volume_percent_limit=self._volume_percent * 100.0) order.mark_cancelled(reason)
def run(config, source_code=None, user_funcs=None): env = Environment(config) persist_helper = None init_succeed = False mod_handler = ModHandler() try: # avoid register handlers everytime # when running in ipython set_loggers(config) basic_system_log.debug("\n" + pformat(config.convert_to_dict())) if source_code is not None: env.set_strategy_loader(SourceCodeStrategyLoader(source_code)) elif user_funcs is not None: env.set_strategy_loader(UserFuncStrategyLoader(user_funcs)) else: env.set_strategy_loader( FileStrategyLoader(config.base.strategy_file)) env.set_global_vars(GlobalVars()) mod_handler.set_env(env) mod_handler.start_up() if not env.data_source: env.set_data_source(BaseDataSource(config.base.data_bundle_path)) env.set_data_proxy(DataProxy(env.data_source)) Scheduler.set_trading_dates_(env.data_source.get_trading_calendar()) scheduler = Scheduler(config.base.frequency) mod_scheduler._scheduler = scheduler env._universe = StrategyUniverse() _adjust_start_date(env.config, env.data_proxy) _validate_benchmark(env.config, env.data_proxy) # FIXME start_dt = datetime.datetime.combine(config.base.start_date, datetime.datetime.min.time()) env.calendar_dt = start_dt env.trading_dt = start_dt broker = env.broker assert broker is not None env.portfolio = broker.get_portfolio() try: env.booking = broker.get_booking() except NotImplementedError: pass env.benchmark_portfolio = create_benchmark_portfolio(env) event_source = env.event_source assert event_source is not None bar_dict = BarMap(env.data_proxy, config.base.frequency) env.set_bar_dict(bar_dict) if env.price_board is None: from .core.bar_dict_price_board import BarDictPriceBoard env.price_board = BarDictPriceBoard() ctx = ExecutionContext(const.EXECUTION_PHASE.GLOBAL) ctx._push() env.event_bus.publish_event(Event(EVENT.POST_SYSTEM_INIT)) scope = create_base_scope(config.base.run_type == RUN_TYPE.BACKTEST) scope.update({"g": env.global_vars}) apis = api_helper.get_apis() scope.update(apis) scope = env.strategy_loader.load(scope) if env.config.extra.enable_profiler: enable_profiler(env, scope) ucontext = StrategyContext() user_strategy = Strategy(env.event_bus, scope, ucontext) scheduler.set_user_context(ucontext) if not config.extra.force_run_init_when_pt_resume: with run_with_user_log_disabled(disabled=config.base.resume_mode): user_strategy.init() if config.extra.context_vars: for k, v in six.iteritems(config.extra.context_vars): setattr(ucontext, k, v) from .core.executor import Executor executor = Executor(env) if config.base.persist: persist_provider = env.persist_provider if persist_provider is None: raise RuntimeError( _(u"Missing persist provider. You need to set persist_provider before use persist" )) persist_helper = PersistHelper(persist_provider, env.event_bus, config.base.persist_mode) env.set_persist_helper(persist_helper) persist_helper.register('core', CoreObjectsPersistProxy(scheduler)) persist_helper.register('user_context', ucontext) persist_helper.register('global_vars', env.global_vars) persist_helper.register('universe', env._universe) if isinstance(event_source, Persistable): persist_helper.register('event_source', event_source) persist_helper.register('portfolio', env.portfolio) if env.benchmark_portfolio: persist_helper.register('benchmark_portfolio', env.benchmark_portfolio) for name, module in six.iteritems(env.mod_dict): if isinstance(module, Persistable): persist_helper.register('mod_{}'.format(name), module) # broker will restore open orders from account if isinstance(broker, Persistable): persist_helper.register('broker', broker) persist_helper.register('executor', executor) env.event_bus.publish_event(Event(EVENT.BEFORE_SYSTEM_RESTORED)) persist_helper.restore() env.event_bus.publish_event(Event(EVENT.POST_SYSTEM_RESTORED)) init_succeed = True # When force_run_init_when_pt_resume is active, # we should run `init` after restore persist data if config.extra.force_run_init_when_pt_resume: assert config.base.resume_mode == True with run_with_user_log_disabled(disabled=False): env._universe._set = set() user_strategy.init() executor.run(bar_dict) if env.profile_deco: output_profile_result(env) except CustomException as e: if init_succeed and env.config.base.persist and persist_helper and env.config.base.persist_mode == const.PERSIST_MODE.ON_CRASH: persist_helper.persist() code = _exception_handler(e) mod_handler.tear_down(code, e) except Exception as e: if init_succeed and env.config.base.persist and persist_helper and env.config.base.persist_mode == const.PERSIST_MODE.ON_CRASH: persist_helper.persist() exc_type, exc_val, exc_tb = sys.exc_info() user_exc = create_custom_exception(exc_type, exc_val, exc_tb, config.base.strategy_file) code = _exception_handler(user_exc) mod_handler.tear_down(code, user_exc) else: if (env.config.base.persist and persist_helper and env.config.base.persist_mode == const.PERSIST_MODE.ON_NORMAL_EXIT): persist_helper.persist() result = mod_handler.tear_down(const.EXIT_CODE.EXIT_SUCCESS) system_log.debug(_(u"strategy run successfully, normal exit")) return result
def _match(self, account, order): order_book_id = order.order_book_id price_board = self._env.price_board last_price = price_board.get_last_price(order_book_id) if not is_valid_price(last_price): instrument = self._env.get_instrument(order_book_id) listed_date = instrument.listed_date.date() if listed_date == self._env.trading_dt.date(): reason = _( "Order Cancelled: current security [{order_book_id}] can not be traded in listed date [{listed_date}]" ).format( order_book_id=order_book_id, listed_date=listed_date, ) else: reason = _( u"Order Cancelled: current bar [{order_book_id}] miss market data." ).format(order_book_id=order_book_id) order.mark_rejected(reason) self._env.event_bus.publish_event( Event(EVENT.ORDER_UNSOLICITED_UPDATE, account=account, order=copy(order))) return if order.type == ORDER_TYPE.LIMIT: deal_price = order.frozen_price else: deal_price = last_price if self._price_limit: """ 在 Signal 模式下,不再阻止涨跌停是否买进,price_limit 参数表示是否给出警告提示。 """ if order.side == SIDE.BUY and deal_price >= price_board.get_limit_up( order_book_id): user_system_log.warning( _(u"You have traded {order_book_id} with {quantity} lots in {bar_status}" ).format(order_book_id=order_book_id, quantity=order.quantity, bar_status=BAR_STATUS.LIMIT_UP)) return if order.side == SIDE.SELL and deal_price <= price_board.get_limit_down( order_book_id): user_system_log.warning( _(u"You have traded {order_book_id} with {quantity} lots in {bar_status}" ).format(order_book_id=order_book_id, quantity=order.quantity, bar_status=BAR_STATUS.LIMIT_DOWN)) return ct_amount = account.positions.get_or_create( order_book_id).cal_close_today_amount(order.quantity, order.side) trade_price = self._slippage_decider.get_trade_price(order, deal_price) trade = Trade.__from_create__(order_id=order.order_id, price=trade_price, amount=order.quantity, side=order.side, position_effect=order.position_effect, order_book_id=order_book_id, frozen_price=order.frozen_price, close_today_amount=ct_amount) trade._commission = self._env.get_trade_commission( account_type_str2enum(account.type), trade) trade._tax = self._env.get_trade_tax( account_type_str2enum(account.type), trade) order.fill(trade) self._env.event_bus.publish_event( Event(EVENT.TRADE, account=account, trade=trade, order=copy(order)))
def publish_settlement(): event_bus.publish_event(PRE_SETTLEMENT) event_bus.publish_event(Event(EVENT.SETTLEMENT)) event_bus.publish_event(POST_SETTLEMENT)
# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from rqrobot.events import EVENT, Event from rqrobot.utils.rq_json import convert_dict_to_json, convert_json_to_dict PRE_BEFORE_TRADING = Event(EVENT.PRE_BEFORE_TRADING) POST_BEFORE_TRADING = Event(EVENT.POST_BEFORE_TRADING) PRE_BAR = Event(EVENT.PRE_BAR) POST_BAR = Event(EVENT.POST_BAR) PRE_TICK = Event(EVENT.PRE_TICK) POST_TICK = Event(EVENT.POST_TICK) PRE_AFTER_TRADING = Event(EVENT.PRE_AFTER_TRADING) POST_AFTER_TRADING = Event(EVENT.POST_AFTER_TRADING) PRE_SETTLEMENT = Event(EVENT.PRE_SETTLEMENT) POST_SETTLEMENT = Event(EVENT.POST_SETTLEMENT) class Executor(object): def __init__(self, env): self._env = env self._last_before_trading = None
def before_trading(self, event): for account, order in self._open_orders: order.active() self._env.event_bus.publish_event( Event(EVENT.ORDER_CREATION_PASS, account=account, order=order))
def events(self, start_date, end_date, frequency): if frequency == "1d": # 根据起始日期和结束日期,获取所有的交易日,然后再循环获取每一个交易日 for day in self._env.data_proxy.get_trading_dates( start_date, end_date): date = day.to_pydatetime() dt_before_trading = date.replace(hour=0, minute=0) dt_bar = self._get_day_bar_dt(date) dt_after_trading = self._get_after_trading_dt(date) yield Event(EVENT.BEFORE_TRADING, calendar_dt=dt_before_trading, trading_dt=dt_before_trading) yield Event(EVENT.BAR, calendar_dt=dt_bar, trading_dt=dt_bar) yield Event(EVENT.AFTER_TRADING, calendar_dt=dt_after_trading, trading_dt=dt_after_trading) elif frequency == '1m': for day in self._env.data_proxy.get_trading_dates( start_date, end_date): before_trading_flag = True date = day.to_pydatetime() last_dt = None done = False dt_before_day_trading = date.replace(hour=8, minute=30) while True: if done: break exit_loop = True trading_minutes = self._get_trading_minutes(date) for calendar_dt in trading_minutes: if last_dt is not None and calendar_dt < last_dt: continue if calendar_dt < dt_before_day_trading: trading_dt = calendar_dt.replace(year=date.year, month=date.month, day=date.day) else: trading_dt = calendar_dt if before_trading_flag: before_trading_flag = False yield Event(EVENT.BEFORE_TRADING, calendar_dt=calendar_dt - datetime.timedelta(minutes=30), trading_dt=trading_dt - datetime.timedelta(minutes=30)) if self._universe_changed: self._universe_changed = False last_dt = calendar_dt exit_loop = False break # yield handle bar yield Event(EVENT.BAR, calendar_dt=calendar_dt, trading_dt=trading_dt) if exit_loop: done = True dt = self._get_after_trading_dt(date) yield Event(EVENT.AFTER_TRADING, calendar_dt=dt, trading_dt=dt) elif frequency == "tick": data_proxy = self._env.data_proxy for day in data_proxy.get_trading_dates(start_date, end_date): date = day.to_pydatetime() last_tick = None last_dt = None dt_before_day_trading = date.replace(hour=8, minute=30) while True: for tick in data_proxy.get_merge_ticks( self._get_universe(), date, last_dt): # find before trading time calendar_dt = tick.datetime if calendar_dt < dt_before_day_trading: trading_dt = calendar_dt.replace(year=date.year, month=date.month, day=date.day) else: trading_dt = calendar_dt if last_tick is None: last_tick = tick yield Event(EVENT.BEFORE_TRADING, calendar_dt=calendar_dt - datetime.timedelta(minutes=30), trading_dt=trading_dt - datetime.timedelta(minutes=30)) if self._universe_changed: self._universe_changed = False break last_dt = calendar_dt yield Event(EVENT.TICK, calendar_dt=calendar_dt, trading_dt=trading_dt, tick=tick) else: break dt = self._get_after_trading_dt(date) yield Event(EVENT.AFTER_TRADING, calendar_dt=dt, trading_dt=dt) else: raise NotImplementedError( _("Frequency {} is not support.").format(frequency))