def parse_future_info(future_info): new_info = {} for underlying_symbol, info in six.iteritems(future_info): try: underlying_symbol = underlying_symbol.upper() except AttributeError: raise RuntimeError(_("Invalid future info: underlying_symbol {} is illegal.".format(underlying_symbol))) for field, value in six.iteritems(info): if field in ( "open_commission_ratio", "close_commission_ratio", "close_commission_today_ratio" ): new_info.setdefault(underlying_symbol, {})[field] = float(value) elif field == "commission_type": if isinstance(value, six.string_types) and value.upper() == "BY_MONEY": new_info.setdefault(underlying_symbol, {})[field] = COMMISSION_TYPE.BY_MONEY elif isinstance(value, six.string_types) and value.upper() == "BY_VOLUME": new_info.setdefault(underlying_symbol, {})[field] = COMMISSION_TYPE.BY_VOLUME elif isinstance(value, COMMISSION_TYPE): new_info.setdefault(underlying_symbol, {})[field] = value else: raise RuntimeError(_( "Invalid future info: commission_type is suppose to be BY_MONEY or BY_VOLUME" )) else: raise RuntimeError(_("Invalid future info: field {} is not valid".format(field))) return new_info
def init_portfolio(env): accounts = {} config = env.config start_date = config.base.start_date total_cash = 0 if DEFAULT_ACCOUNT_TYPE.FUTURE.name in config.base.accounts and config.base.frequency != '1d': BaseAccount.AGGRESSIVE_UPDATE_LAST_PRICE = True for account_type, starting_cash in six.iteritems(config.base.accounts): if account_type == DEFAULT_ACCOUNT_TYPE.STOCK.name: if starting_cash == 0: raise RuntimeError(_(u"stock starting cash can not be 0, using `--account stock 100000`")) StockAccount = env.get_account_model(DEFAULT_ACCOUNT_TYPE.STOCK.name) StockPosition = env.get_position_model(DEFAULT_ACCOUNT_TYPE.STOCK.name) accounts[DEFAULT_ACCOUNT_TYPE.STOCK.name] = StockAccount(starting_cash, Positions(StockPosition)) total_cash += starting_cash elif account_type == DEFAULT_ACCOUNT_TYPE.FUTURE.name: if starting_cash == 0: raise RuntimeError(_(u"future starting cash can not be 0, using `--account future 100000`")) FutureAccount = env.get_account_model(DEFAULT_ACCOUNT_TYPE.FUTURE.name) FuturePosition = env.get_position_model(DEFAULT_ACCOUNT_TYPE.FUTURE.name) accounts[DEFAULT_ACCOUNT_TYPE.FUTURE.name] = FutureAccount(starting_cash, Positions(FuturePosition)) total_cash += starting_cash else: raise NotImplementedError return Portfolio(start_date, 1, total_cash, accounts)
def start_up(self, env, mod_config): mod_config.matching_type = self.parse_matching_type(mod_config.matching_type) if mod_config.commission_multiplier < 0: raise patch_user_exc(ValueError(_(u"invalid commission multiplier value: value range is [0, +∞)"))) if env.config.base.margin_multiplier <= 0: raise patch_user_exc(ValueError(_(u"invalid margin multiplier value: value range is (0, +∞]"))) if env.config.base.frequency == "tick": mod_config.volume_limit = False if mod_config.matching_type not in [ MATCHING_TYPE.NEXT_TICK_LAST, MATCHING_TYPE.NEXT_TICK_BEST_OWN, MATCHING_TYPE.NEXT_TICK_BEST_COUNTERPARTY, ]: raise RuntimeError(_("Not supported matching type {}").format(mod_config.matching_type)) else: if mod_config.matching_type not in [ MATCHING_TYPE.NEXT_BAR_OPEN, MATCHING_TYPE.CURRENT_BAR_CLOSE, ]: raise RuntimeError(_("Not supported matching type {}").format(mod_config.matching_type)) if mod_config.signal: env.set_broker(SignalBroker(env, mod_config)) else: env.set_broker(SimulationBroker(env, mod_config)) event_source = SimulationEventSource(env, env.config.base.account_list) env.set_event_source(event_source)
def check_are_valid_fields(func_name, fields): if isinstance(fields, six.string_types): if fields not in valid_fields: raise RQInvalidArgument( _(u"function {}: invalid {} argument, valid fields are {}, got {} (type: {})").format( func_name, self._arg_name, repr(valid_fields), fields, type(fields) )) return if fields is None and ignore_none: return if isinstance(fields, list): invalid_fields = [field for field in fields if field not in valid_fields] if invalid_fields: raise RQInvalidArgument( _(u"function {}: invalid field {}, valid fields are {}, got {} (type: {})").format( func_name, invalid_fields, repr(valid_fields), fields, type(fields) )) return raise RQInvalidArgument( _(u"function {}: invalid {} argument, expect a string or a list of string, got {} (type: {})").format( func_name, self._arg_name, repr(fields), type(fields) ))
def submit_order(id_or_ins, amount, side, price=None, position_effect=None): order_book_id = assure_order_book_id(id_or_ins) env = Environment.get_instance() if env.config.base.run_type != RUN_TYPE.BACKTEST: if "88" in order_book_id: raise RQInvalidArgument(_(u"Main Future contracts[88] are not supported in paper trading.")) if "99" in order_book_id: raise RQInvalidArgument(_(u"Index Future contracts[99] are not supported in paper trading.")) style = cal_style(price, None) market_price = env.get_last_price(order_book_id) if not is_valid_price(market_price): user_system_log.warn( _(u"Order Creation Failed: [{order_book_id}] No market data").format(order_book_id=order_book_id) ) return order = Order.__from_create__( order_book_id=order_book_id, quantity=amount, side=side, style=style, position_effect=position_effect ) if order.type == ORDER_TYPE.MARKET: order.set_frozen_price(market_price) if env.can_submit_order(order): env.broker.submit_order(order) return order
def update_bundle(data_bundle_path=None, locale="zh_Hans_CN", confirm=True): set_locale(locale) default_bundle_path = os.path.abspath(os.path.expanduser('~/.rqalpha/bundle')) if data_bundle_path is None: data_bundle_path = default_bundle_path else: data_bundle_path = os.path.abspath(os.path.join(data_bundle_path, './bundle/')) if (confirm and os.path.exists(data_bundle_path) and data_bundle_path != default_bundle_path and os.listdir(data_bundle_path)): click.confirm(_(u""" [WARNING] Target bundle path {data_bundle_path} is not empty. The content of this folder will be REMOVED before updating. Are you sure to continue?""").format(data_bundle_path=data_bundle_path), abort=True) tmp = os.path.join(tempfile.gettempdir(), 'rq.bundle') url, total_length = get_exactly_url() out = open(tmp, 'wb') down_load_helper(out, total_length, url) out.close() shutil.rmtree(data_bundle_path, ignore_errors=True) os.makedirs(data_bundle_path) tar = tarfile.open(tmp, 'r:bz2') tar.extractall(data_bundle_path) tar.close() os.remove(tmp) six.print_(_(u"Data bundle download successfully in {bundle_path}").format(bundle_path=data_bundle_path))
def order_value(id_or_ins, cash_amount, price=None, style=None): """ 使用想要花费的金钱买入/卖出股票,而不是买入/卖出想要的股数,正数代表买入,负数代表卖出。股票的股数总是会被调整成对应的100的倍数(在A中国A股市场1手是100股)。当您提交一个卖单时,该方法代表的意义是您希望通过卖出该股票套现的金额。如果金额超出了您所持有股票的价值,那么您将卖出所有股票。需要注意,如果资金不足,该API将不会创建发送订单。 :param id_or_ins: 下单标的物 :type id_or_ins: :class:`~Instrument` object | `str` :param float cash_amount: 需要花费现金购买/卖出证券的数目。正数代表买入,负数代表卖出。 :param float price: 下单价格,默认为None,表示 :class:`~MarketOrder`, 此参数主要用于简化 `style` 参数。 :param style: 下单类型, 默认是市价单。目前支持的订单类型有 :class:`~LimitOrder` 和 :class:`~MarketOrder` :type style: `OrderStyle` object :return: :class:`~Order` object :example: .. code-block:: python #买入价值¥10000的平安银行股票,并以市价单发送。如果现在平安银行股票的价格是¥7.5,那么下面的代码会买入1300股的平安银行,因为少于100股的数目将会被自动删除掉: order_value('000001.XSHE', 10000) #卖出价值¥10000的现在持有的平安银行: order_value('000001.XSHE', -10000) """ style = cal_style(price, style) if isinstance(style, LimitOrder): if style.get_limit_price() <= 0: raise RQInvalidArgument(_(u"Limit order price should be positive")) order_book_id = assure_stock_order_book_id(id_or_ins) env = Environment.get_instance() price = env.get_last_price(order_book_id) if not is_valid_price(price): user_system_log.warn( _(u"Order Creation Failed: [{order_book_id}] No market data").format(order_book_id=order_book_id)) return account = env.portfolio.accounts[DEFAULT_ACCOUNT_TYPE.STOCK.name] round_lot = int(env.get_instrument(order_book_id).round_lot) if cash_amount > 0: cash_amount = min(cash_amount, account.cash) if isinstance(style, MarketOrder): amount = int(Decimal(cash_amount) / Decimal(price) / Decimal(round_lot)) * round_lot else: amount = int(Decimal(cash_amount) / Decimal(style.get_limit_price()) / Decimal(round_lot)) * round_lot # if the cash_amount is larger than you current security’s position, # then it will sell all shares of this security. position = account.positions[order_book_id] amount = downsize_amount(amount, position) return order_shares(order_book_id, amount, style=style)
def can_submit_order(self, order, account=None): if order.type != ORDER_TYPE.LIMIT: return True # FIXME: it may be better to round price in data source limit_up = round(self._env.price_board.get_limit_up(order.order_book_id), 4) if order.price > limit_up: reason = _( "Order Creation Failed: limit order price {limit_price} is higher than limit up {limit_up}." ).format( limit_price=order.price, limit_up=limit_up ) user_system_log.warn(reason) return False limit_down = round(self._env.price_board.get_limit_down(order.order_book_id), 4) if order.price < limit_down: reason = _( "Order Creation Failed: limit order price {limit_price} is lower than limit down {limit_down}." ).format( limit_price=order.price, limit_down=limit_down ) user_system_log.warn(reason) return False return True
def _future_validator(account, order): if order.position_effect == POSITION_EFFECT.OPEN: return True position = account.positions[order.order_book_id] if order.side == SIDE.BUY and order.quantity > position.closable_sell_quantity: order.mark_rejected(_( "Order Rejected: not enough securities {order_book_id} to buy close, target" " sell quantity is {quantity}, sell_closable_quantity {closable}").format( order_book_id=order.order_book_id, quantity=order.quantity, closable=position.closable_sell_quantity, )) return False elif order.side == SIDE.SELL and order.quantity > position.closable_buy_quantity: order.mark_rejected(_( "Order Rejected: not enough securities {order_book_id} to sell close, target" " sell quantity is {quantity}, buy_closable_quantity {closable}").format( order_book_id=order.order_book_id, quantity=order.quantity, closable=position.closable_buy_quantity, )) return False return True
def __init__(self, event_bus, scope, ucontext): self._user_context = ucontext self._current_universe = set() self._init = scope.get('init', None) self._handle_bar = scope.get('handle_bar', None) self._handle_tick = scope.get('handle_tick', None) func_before_trading = scope.get('before_trading', None) if func_before_trading is not None and func_before_trading.__code__.co_argcount > 1: self._before_trading = lambda context: func_before_trading(context, None) user_system_log.warn(_(u"deprecated parameter[bar_dict] in before_trading function.")) else: self._before_trading = func_before_trading self._after_trading = scope.get('after_trading', None) if self._before_trading is not None: event_bus.add_listener(EVENT.BEFORE_TRADING, self.before_trading) if self._handle_bar is not None: event_bus.add_listener(EVENT.BAR, self.handle_bar) if self._handle_tick is not None: event_bus.add_listener(EVENT.TICK, self.handle_tick) if self._after_trading is not None: event_bus.add_listener(EVENT.AFTER_TRADING, self.after_trading) self._before_day_trading = scope.get('before_day_trading', None) self._before_night_trading = scope.get('before_night_trading', None) if self._before_day_trading is not None: user_system_log.warn(_(u"[deprecated] before_day_trading is no longer used. use before_trading instead.")) if self._before_night_trading is not None: user_system_log.warn(_(u"[deprecated] before_night_trading is no longer used. use before_trading instead.")) self._force_run_before_trading = Environment.get_instance().config.extra.force_run_init_when_pt_resume
def _match(self, account, order): # TODO support tick cal env = Environment.get_instance() bar = env.get_bar(order.order_book_id) bar_status = bar._bar_status if bar_status == BAR_STATUS.ERROR: listed_date = bar.instrument.listed_date.date() if listed_date == self._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.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) self._env.event_bus.publish_event(Event(EVENT.ORDER_UNSOLICITED_UPDATE, account=account, order=order)) return if isinstance(order.style, LimitOrder): deal_price = order.style.get_limit_price() else: deal_price = bar.close deal_price = min(deal_price, bar.high) deal_price = max(deal_price, bar.low) deal_price = self._slippage_decider.get_trade_price(order.side, deal_price) if (order.side == SIDE.BUY and bar_status == BAR_STATUS.LIMIT_UP) or ( order.side == SIDE.SELL and bar_status == BAR_STATUS.LIMIT_DOWN): user_system_log.warning(_(u"You have traded {order_book_id} with {quantity} lots in {bar_status}").format( order_book_id=order.order_book_id, quantity=order.quantity, bar_status=bar_status )) ct_amount = account.portfolio.positions.get_or_create(order.order_book_id).cal_close_today_amount(order.quantity, order.side) trade = Trade.__from_create__( order_id=order.order_id, calendar_dt=self._calendar_dt, trading_dt=self._trading_dt, price=deal_price, amount=order.quantity, 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._commission_decider.get_commission(account.type, trade) trade._tax = self._tax_decider.get_tax(account.type, trade) order.fill(trade) env.event_bus.publish_event(Event(EVENT.TRADE, account=account, trade=trade))
def _exception_handler(e): better_exceptions.excepthook(e.error.exc_type, e.error.exc_val, e.error.exc_tb) user_system_log.error(e.error) if not is_user_exc(e.error.exc_val): code = const.EXIT_CODE.EXIT_INTERNAL_ERROR system_log.exception(_(u"strategy execute exception")) else: code = const.EXIT_CODE.EXIT_USER_ERROR user_detail_log.exception(_(u"strategy execute exception")) return code
def assure_future_order_book_id(id_or_symbols): if isinstance(id_or_symbols, Instrument): if id_or_symbols.type != "Future": raise RQInvalidArgument( _(u"{order_book_id} is not supported in current strategy type").format( order_book_id=id_or_symbols.order_book_id)) else: return id_or_symbols.order_book_id elif isinstance(id_or_symbols, six.string_types): return assure_future_order_book_id(instruments(id_or_symbols)) else: raise RQInvalidArgument(_(u"unsupported order_book_id type"))
def tear_down(self, *args): result = {} for mod_name, __ in reversed(self._mod_list): try: basic_system_log.debug(_(u"mod tear_down [START] {}").format(mod_name)) ret = self._mod_dict[mod_name].tear_down(*args) basic_system_log.debug(_(u"mod tear_down [END] {}").format(mod_name)) except Exception as e: system_log.exception("tear down fail for {}", mod_name) continue if ret is not None: result[mod_name] = ret return result
def start_up(self, env, mod_config): mod_config.matching_type = self.parse_matching_type(mod_config.matching_type) if mod_config.commission_multiplier < 0: raise patch_user_exc(ValueError(_(u"invalid commission multiplier value: value range is [0, +∞)"))) if env.config.base.margin_multiplier <= 0: raise patch_user_exc(ValueError(_(u"invalid margin multiplier value: value range is (0, +∞]"))) if mod_config.signal: env.set_broker(SignalBroker(env, mod_config)) else: env.set_broker(SimulationBroker(env, mod_config)) event_source = SimulationEventSource(env, env.config.base.account_list) env.set_event_source(event_source)
def _exception_handler(e): try: sys.excepthook(e.error.exc_type, e.error.exc_val, e.error.exc_tb) except Exception as e: system_log.exception("hook exception failed") user_system_log.error(e.error) if not is_user_exc(e.error.exc_val): code = const.EXIT_CODE.EXIT_INTERNAL_ERROR system_log.error(_(u"strategy execute exception"), exc=e) else: code = const.EXIT_CODE.EXIT_USER_ERROR user_detail_log.error(_(u"strategy execute exception"), exc=e) return code
def to_position_list(positions): result = [] for s in positions.split(','): try: order_book_id, quantity = s.split(':') except ValueError: raise RuntimeError(_(u"invalid init position {}, should be in format 'order_book_id:quantity'").format(s)) try: result.append((order_book_id, float(quantity))) except ValueError: raise RuntimeError(_(u"invalid quantity for instrument {order_book_id}: {quantity}").format( order_book_id=order_book_id, quantity=quantity)) return result
def _future_validator(account, order): if order.position_effect == POSITION_EFFECT.OPEN: return True position = account.positions[order.order_book_id] if order.side == SIDE.BUY and order.position_effect == POSITION_EFFECT.CLOSE_TODAY \ and order.quantity > position._closable_today_sell_quantity: user_system_log.warn(_( "Order Creation Failed: not enough today position {order_book_id} to buy close, target" " quantity is {quantity}, closable today quantity {closable}").format( order_book_id=order.order_book_id, quantity=order.quantity, closable=position._closable_today_sell_quantity, )) return False if order.side == SIDE.SELL and order.position_effect == POSITION_EFFECT.CLOSE_TODAY \ and order.quantity > position._closable_today_buy_quantity: user_system_log.warn(_( "Order Creation Failed: not enough today position {order_book_id} to sell close, target" " quantity is {quantity}, closable today quantity {closable}").format( order_book_id=order.order_book_id, quantity=order.quantity, closable=position._closable_today_buy_quantity, )) return False if order.side == SIDE.BUY and order.quantity > position.closable_sell_quantity: user_system_log.warn(_( "Order Creation Failed: not enough securities {order_book_id} to buy close, target" " sell quantity is {quantity}, sell_closable_quantity {closable}").format( order_book_id=order.order_book_id, quantity=order.quantity, closable=position.closable_sell_quantity, )) return False elif order.side == SIDE.SELL and order.quantity > position.closable_buy_quantity: user_system_log.warn(_( "Order Creation Failed: not enough securities {order_book_id} to sell close, target" " sell quantity is {quantity}, buy_closable_quantity {closable}").format( order_book_id=order.order_book_id, quantity=order.quantity, closable=position.closable_buy_quantity, )) return False return True
def init_portfolio(env): accounts = {} config = env.config start_date = datetime.datetime.combine(config.base.start_date, datetime.time.min) units = 0 if config.base.init_positions or (DEFAULT_ACCOUNT_TYPE.FUTURE.name in config.base.accounts and config.base.frequency != '1d'): BaseAccount.AGGRESSIVE_UPDATE_LAST_PRICE = True for account_type, starting_cash in six.iteritems(config.base.accounts): if starting_cash == 0: raise RuntimeError(_(u"{} starting cash can not be 0, using `--account {} 100000`").format(account_type, account_type)) account_model = env.get_account_model(account_type) position_model = env.get_position_model(account_type) positions = Positions(position_model) for order_book_id, quantity in _filter_positions(env, account_type): instrument = env.get_instrument(order_book_id) if instrument is None: raise RuntimeError(_(u'invalid order book id {} in initial positions').format(order_book_id)) if not instrument.listing: raise RuntimeError(_(u'instrument {} in initial positions is not listing').format(order_book_id)) bars = env.data_proxy.history_bars(order_book_id, 1, '1d', 'close', env.data_proxy.get_previous_trading_date(start_date), adjust_type='none') if bars is None: raise RuntimeError(_(u'the close price of {} in initial positions is not available').format(order_book_id)) price = bars[0] trade = _fake_trade(order_book_id, quantity, price) if order_book_id not in positions: positions[order_book_id] = position_model(order_book_id) positions[order_book_id].apply_trade(trade) # FIXME positions[order_book_id]._last_price = price # 变成昨仓 for order_book_id, position in positions.items(): position.apply_settlement() account = account_model(starting_cash, positions) units += account.total_value accounts[account_type] = account return Portfolio(config.base.start_date, 1, units, accounts)
def clock_worker(self): data_proxy = self._env.data_proxy while True: # wait for the first data ready if data_proxy.current_snapshot("000001.XSHG", None, None).datetime.date() == datetime.date.today(): system_log.info(_("Market data is ready, start to work now!")) break time.sleep(0.1) while True: time.sleep(self.fps) if is_holiday_today(): time.sleep(60) continue dt = datetime.datetime.now() if dt.strftime("%H:%M:%S") >= "08:30:00" and dt.date() > self.before_trading_fire_date: self.event_queue.put((dt, EVENT.BEFORE_TRADING)) self.before_trading_fire_date = dt.date() elif dt.strftime("%H:%M:%S") >= "15:10:00" and dt.date() > self.after_trading_fire_date: self.event_queue.put((dt, EVENT.AFTER_TRADING)) self.after_trading_fire_date = dt.date() elif dt.strftime("%H:%M:%S") >= "15:10:00" and dt.date() > self.settlement_fire_date: self.event_queue.put((dt, EVENT.SETTLEMENT)) self.settlement_fire_date = dt.date() if is_tradetime_now(): self.event_queue.put((dt, EVENT.BAR))
def get_positions(): booking = Environment.get_instance().booking if not booking: raise RuntimeError( _("Booking has not been set, please check your broker configuration.") ) return booking.get_positions()
def order_shares(id_or_ins, amount, price=None, style=None): order_book_id = assure_order_book_id(id_or_ins) env = Environment.get_instance() if amount > 0: side = SIDE.BUY position_effect = POSITION_EFFECT.OPEN else: amount = abs(amount) side = SIDE.SELL position_effect = POSITION_EFFECT.CLOSE round_lot = int(env.get_instrument(order_book_id).round_lot) try: amount = int(Decimal(amount) / Decimal(round_lot)) * round_lot except ValueError: amount = 0 order = Order.__from_create__(order_book_id, amount, side, style, position_effect) if amount == 0: # 如果计算出来的下单量为0, 则不生成Order, 直接返回None # 因为很多策略会直接在handle_bar里面执行order_target_percent之类的函数,经常会出现下一个量为0的订单,如果这些订单都生成是没有意义的。 order.mark_rejected(_(u"Order Creation Failed: 0 order quantity")) return order env.broker.submit_order(order) return order
def order_percent(id_or_ins, percent, price=None, style=None): """ 发送一个花费价值等于目前投资组合(市场价值和目前现金的总和)一定百分比现金的买/卖单,正数代表买,负数代表卖。股票的股数总是会被调整成对应的一手的股票数的倍数(1手是100股)。百分比是一个小数,并且小于或等于1(<=100%),0.5表示的是50%.需要注意,如果资金不足,该API将不会创建发送订单。 需要注意: 发送买单时,percent 代表的是期望买入股票消耗的金额(包含税费)占投资组合总权益的比例。 发送卖单时,percent 代表的是期望卖出的股票总价值占投资组合总权益的比例。 :param id_or_ins: 下单标的物 :type id_or_ins: :class:`~Instrument` object | `str` :param float percent: 占有现有的投资组合价值的百分比。正数表示买入,负数表示卖出。 :param float price: 下单价格,默认为None,表示 :class:`~MarketOrder`, 此参数主要用于简化 `style` 参数。 :param style: 下单类型, 默认是市价单。目前支持的订单类型有 :class:`~LimitOrder` 和 :class:`~MarketOrder` :type style: `OrderStyle` object :return: :class:`~Order` object | None :example: .. code-block:: python #花费等于现有投资组合50%价值的现金买入平安银行股票: order_percent('000001.XSHG', 0.5) """ if percent < -1 or percent > 1: raise RQInvalidArgument(_(u"percent should between -1 and 1")) style = cal_style(price, style) account = Environment.get_instance().portfolio.accounts[DEFAULT_ACCOUNT_TYPE.STOCK.name] return order_value(id_or_ins, account.total_value * percent, style=style)
def order_percent(id_or_ins, percent, price=None, style=None): """ 发送一个等于目前投资组合价值(市场价值和目前现金的总和)一定百分比的买/卖单,正数代表买,负数代表卖。股票的股数总是会被调整成对应的一手的股票数的倍数(1手是100股)。百分比是一个小数,并且小于或等于1(<=100%),0.5表示的是50%.需要注意,如果资金不足,该API将不会创建发送订单。 :param id_or_ins: 下单标的物 :type id_or_ins: :class:`~Instrument` object | `str` :param float percent: 占有现有的投资组合价值的百分比。正数表示买入,负数表示卖出。 :param float price: 下单价格,默认为None,表示 :class:`~MarketOrder`, 此参数主要用于简化 `style` 参数。 :param style: 下单类型, 默认是市价单。目前支持的订单类型有 :class:`~LimitOrder` 和 :class:`~MarketOrder` :type style: `OrderStyle` object :return: :class:`~Order` object :example: .. code-block:: python #买入等于现有投资组合50%价值的平安银行股票。如果现在平安银行的股价是¥10/股并且现在的投资组合总价值是¥2000,那么将会买入200股的平安银行股票。(不包含交易成本和滑点的损失): order_percent('000001.XSHG', 0.5) """ if percent < -1 or percent > 1: raise RQInvalidArgument(_(u"percent should between -1 and 1")) style = cal_style(price, style) account = Environment.get_instance().portfolio.accounts[DEFAULT_ACCOUNT_TYPE.STOCK.name] return order_value(id_or_ins, account.total_value * percent, style=style)
def assure_stock_order_book_id(id_or_symbols): if isinstance(id_or_symbols, Instrument): return id_or_symbols.order_book_id elif isinstance(id_or_symbols, six.string_types): return assure_stock_order_book_id(instruments(id_or_symbols)) else: raise RQInvalidArgument(_(u"unsupported order_book_id type"))
def set_env(self, environment): self._env = environment config = environment.config for mod_name in config.mod.__dict__: mod_config = getattr(config.mod, mod_name) if not mod_config.enabled: continue self._mod_list.append((mod_name, mod_config)) for idx, (mod_name, user_mod_config) in enumerate(self._mod_list): if hasattr(user_mod_config, 'lib'): lib_name = user_mod_config.lib elif mod_name in SYSTEM_MOD_LIST: lib_name = "rqalpha.mod.rqalpha_mod_" + mod_name else: lib_name = "rqalpha_mod_" + mod_name system_log.debug(_(u"loading mod {}").format(lib_name)) mod_module = import_mod(lib_name) if mod_module is None: del self._mod_list[idx] return mod = mod_module.load_mod() mod_config = RqAttrDict(copy.deepcopy(getattr(mod_module, "__config__", {}))) mod_config.update(user_mod_config) setattr(config.mod, mod_name, mod_config) self._mod_list[idx] = (mod_name, mod_config) self._mod_dict[mod_name] = mod self._mod_list.sort(key=lambda item: getattr(item[1], "priority", 100)) environment.mod_dict = self._mod_dict
def _get_transaction_cost_decider(self, account_type): try: return self._transaction_cost_decider_dict[account_type] except KeyError: raise NotImplementedError(_(u"No such transaction cost decider for such account_type {}.".format( account_type )))
def check_portfolio_exist(func_name): if Environment.get_instance().portfolio is None: raise RQApiNotSupportedError(_( "Api {} cannot be called in current strategy, because portfolio instance dose not exist".format( func_name ) ))
def get_bars(self, order_book_id, fields=None): try: s, e = self._index[order_book_id] except KeyError: six.print_(_(u"No data for {}").format(order_book_id)) return if fields is None: # the first is date fields = self._table.names[1:] if len(fields) == 1: return self._converter.convert(fields[0], self._table.cols[fields[0]][s:e]) # remove datetime if exist in fields self._remove_(fields, 'datetime') dtype = np.dtype([('datetime', np.uint64)] + [(f, self._converter.field_type(f, self._table.cols[f].dtype)) for f in fields]) result = np.empty(shape=(e - s, ), dtype=dtype) for f in fields: result[f] = self._converter.convert(f, self._table.cols[f][s:e]) result['datetime'] = self._table.cols['date'][s:e] result['datetime'] *= 1000000 return result
def __from_create__( cls, order_id, price, amount, side, position_effect, order_book_id, commission=0., tax=0., trade_id=None, close_today_amount=0, frozen_price=0, calendar_dt=None, trading_dt=None ): trade = cls() trade_id = trade_id or next(trade.trade_id_gen) for value in (price, amount, commission, tax, frozen_price): if value != value: raise RuntimeError(_( "price, amount, commission, tax and frozen_price of trade {trade_id} is not supposed to be nan, " "current_value is {price}, {amount}, {commission}, {tax}, {frozen_price}" ).format( trade_id=trade_id, price=price, amount=amount, commission=commission, tax=tax, frozen_price=frozen_price )) env = Environment.get_instance() trade._calendar_dt = calendar_dt or env.calendar_dt trade._trading_dt = trading_dt or env.trading_dt trade._price = price trade._amount = amount trade._order_id = order_id trade._commission = commission trade._tax = tax trade._trade_id = trade_id trade._close_today_amount = close_today_amount trade._side = side trade._position_effect = position_effect trade._order_book_id = order_book_id trade._frozen_price = frozen_price return trade
def raise_invalid_instrument_error(self, func_name, value): return self.raise_instrument_error(func_name, value, _("valid order_book_id/instrument"))
def _submit_order(id_or_ins, amount, side, position_effect, style): amount = int(amount) if amount == 0: user_system_log.warn(_(u"Order Creation Failed: Order amount is 0.")) return None if isinstance(style, LimitOrder) and style.get_limit_price() <= 0: raise RQInvalidArgument(_(u"Limit order price should be positive")) instrument = assure_instrument(id_or_ins) order_book_id = instrument.order_book_id env = Environment.get_instance() if env.config.base.run_type != RUN_TYPE.BACKTEST and instrument.type == INSTRUMENT_TYPE.FUTURE: if "88" in order_book_id: raise RQInvalidArgument( _(u"Main Future contracts[88] are not supported in paper trading." )) if "99" in order_book_id: raise RQInvalidArgument( _(u"Index Future contracts[99] are not supported in paper trading." )) price = env.get_last_price(order_book_id) if not is_valid_price(price): user_system_log.warn( _(u"Order Creation Failed: [{order_book_id}] No market data"). format(order_book_id=order_book_id)) return env = Environment.get_instance() orders = [] if position_effect in (POSITION_EFFECT.CLOSE_TODAY, POSITION_EFFECT.CLOSE): direction = POSITION_DIRECTION.LONG if side == SIDE.SELL else POSITION_DIRECTION.SHORT position = env.portfolio.get_position(order_book_id, direction) # type: Position if position_effect == POSITION_EFFECT.CLOSE_TODAY: if amount > position.today_closable: user_system_log.warning( _("Order Creation Failed: " "close today amount {amount} is larger than today closable quantity {quantity}" ).format(amount=amount, quantity=position.today_closable)) return [] orders.append( Order.__from_create__(order_book_id, amount, side, style, POSITION_EFFECT.CLOSE_TODAY)) else: quantity, old_quantity = position.quantity, position.old_quantity if amount > quantity: user_system_log.warn( _(u"Order Creation Failed: close amount {amount} is larger than position quantity {quantity}" ).format(amount=amount, quantity=quantity)) return [] if amount > old_quantity: if old_quantity != 0: # 如果有昨仓,则创建一个 POSITION_EFFECT.CLOSE 的平仓单 orders.append( Order.__from_create__(order_book_id, old_quantity, side, style, POSITION_EFFECT.CLOSE)) # 剩下还有仓位,则创建一个 POSITION_EFFECT.CLOSE_TODAY 的平今单 orders.append( Order.__from_create__(order_book_id, amount - old_quantity, side, style, POSITION_EFFECT.CLOSE_TODAY)) else: # 创建 POSITION_EFFECT.CLOSE 的平仓单 orders.append( Order.__from_create__(order_book_id, amount, side, style, POSITION_EFFECT.CLOSE)) elif position_effect == POSITION_EFFECT.OPEN: orders.append( Order.__from_create__(order_book_id, amount, side, style, position_effect)) else: raise NotImplementedError() if len(orders) > 1: user_system_log.warn( _("Order was separated, original order: {original_order_repr}, new orders: [{new_orders_repr}]" ). format( original_order_repr= "Order(order_book_id={}, quantity={}, side={}, position_effect={})" .format(order_book_id, amount, side, position_effect), new_orders_repr=", ".join([ "Order({}, {}, {}, {})".format(o.order_book_id, o.quantity, o.side, o.position_effect) for o in orders ]))) for o in orders: if o.type == ORDER_TYPE.MARKET: o.set_frozen_price(price) if env.can_submit_order(o): env.broker.submit_order(o) else: orders.remove(o) # 向前兼容,如果创建的order_list 只包含一个订单的话,直接返回对应的订单,否则返回列表 if len(orders) == 1: return orders[0] else: return orders
def events(self, start_date, end_date, frequency): trading_dates = self._env.data_proxy.get_trading_dates( start_date, end_date) if frequency == "1d": # 根据起始日期和结束日期,获取所有的交易日,然后再循环获取每一个交易日 for day in trading_dates: 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.OPEN_AUCTION, 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 trading_dates: 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 - timedelta(minutes=30), trading_dt=trading_dt - timedelta(minutes=30)) yield Event( EVENT.OPEN_AUCTION, calendar_dt=calendar_dt - timedelta(minutes=3), trading_dt=trading_dt - timedelta(minutes=3), ) 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 trading_dates: 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 - timedelta(minutes=30), trading_dt=trading_dt - timedelta(minutes=30)) yield Event( EVENT.OPEN_AUCTION, calendar_dt=calendar_dt - timedelta(minutes=3), trading_dt=trading_dt - timedelta(minutes=3), ) 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))
def start_up(self): for mod_name, mod_config in self._mod_list: system_log.debug( _(u"mod start_up [START] {}\n{}").format(mod_name, mod_config)) self._mod_dict[mod_name].start_up(self._env, mod_config) system_log.debug(_(u"mod start_up [END] {}").format(mod_name))
def short_selling_allowed(self, value): user_system_log.warn( _(u"[abandon] {} is no longer used.").format( 'context.short_selling_allowed'))
def margin_rate(self, value): user_system_log.warn( _(u"[abandon] {} is no longer used.").format( 'context.margin_rate'))
def daily_realized_pnl(self): """ [已弃用] 请使用 realized_pnl """ user_system_log.warn(_(u"[abandon] {} is no longer used.").format('future_account.daily_realized_pnl')) return self.realized_pnl
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) init_rqdatac(getattr(config.base, 'rqdatac_uri', None)) system_log.debug("\n" + pformat(config.convert_to_dict())) env.set_strategy_loader( init_strategy_loader(env, source_code, user_funcs, config)) mod_handler.set_env(env) mod_handler.start_up() if not env.data_source: env.set_data_source( BaseDataSource(config.base.data_bundle_path, getattr(config.base, "future_info", {}))) if env.price_board is None: from rqalpha.data.bar_dict_price_board import BarDictPriceBoard env.price_board = BarDictPriceBoard() env.set_data_proxy(DataProxy(env.data_source, env.price_board)) _adjust_start_date(env.config, env.data_proxy) ctx = ExecutionContext(const.EXECUTION_PHASE.GLOBAL) ctx._push() # FIXME start_dt = datetime.datetime.combine(config.base.start_date, datetime.datetime.min.time()) env.calendar_dt = start_dt env.trading_dt = start_dt assert env.broker is not None assert env.event_source is not None if env.portfolio is None: from rqalpha.portfolio import Portfolio env.set_portfolio( Portfolio(config.base.accounts, config.base.init_positions)) env.event_bus.publish_event(Event(EVENT.POST_SYSTEM_INIT)) scope = create_base_scope() scope.update({"g": env.global_vars}) scope.update(get_strategy_apis()) scope = env.strategy_loader.load(scope) if config.extra.enable_profiler: enable_profiler(env, scope) ucontext = StrategyContext() executor = Executor(env) persist_helper = init_persist_helper(env, ucontext, executor, config) user_strategy = Strategy(env.event_bus, scope, ucontext) env.user_strategy = user_strategy env.event_bus.publish_event(Event(EVENT.BEFORE_STRATEGY_RUN)) if config.extra.context_vars: for k, v in config.extra.context_vars.items(): if isinstance(v, RqAttrDict): v = v.__dict__ setattr(ucontext, k, v) if persist_helper: with LogCapture(user_log) as log_capture: user_strategy.init() else: user_strategy.init() if persist_helper: env.event_bus.publish_event(Event(EVENT.BEFORE_SYSTEM_RESTORED)) restored_obj_state = persist_helper.restore(None) check_key = ["global_vars", "user_context", "executor", "universe"] kept_current_init_data = not any( v for k, v in restored_obj_state.items() if k in check_key) system_log.debug( "restored_obj_state: {}".format(restored_obj_state)) system_log.debug( "kept_current_init_data: {}".format(kept_current_init_data)) if kept_current_init_data: # 未能恢复init相关数据 保留当前策略初始化变量(展示当前策略初始化日志) log_capture.replay() else: system_log.debug(_('system restored')) env.event_bus.publish_event(Event(EVENT.POST_SYSTEM_RESTORED)) init_succeed = True bar_dict = BarMap(env.data_proxy, config.base.frequency) executor.run(bar_dict) env.event_bus.publish_event(Event(EVENT.POST_STRATEGY_RUN)) if env.profile_deco: output_profile_result(env) release_print(scope) except CustomException as e: if init_succeed 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: system_log.error(traceback.format_exc()) if init_succeed 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 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 _set_env_and_data_source(self): env = self._env mod_config = self._mod_config system_log.info("use recorder {}", mod_config.recorder) if mod_config.recorder == "CsvRecorder": if not mod_config.persist_folder: raise RuntimeError( _(u"You need to set persist_folder to use CsvRecorder")) persist_provider = DiskPersistProvider( os.path.join(mod_config.persist_folder, "persist")) self._recorder = recorders.CsvRecorder(mod_config.persist_folder) elif mod_config.recorder == "MongodbRecorder": if mod_config.strategy_id is None: raise RuntimeError(_(u"You need to set strategy_id")) persist_provider = persist_providers.MongodbPersistProvider( mod_config.strategy_id, mod_config.mongo_url, mod_config.mongo_dbname) self._recorder = recorders.MongodbRecorder(mod_config.strategy_id, mod_config.mongo_url, mod_config.mongo_dbname) else: raise RuntimeError( _(u"unknown recorder {}").format(mod_config.recorder)) if env.persist_provider is None: env.set_persist_provider(persist_provider) self._meta = { "strategy_id": mod_config.strategy_id, "origin_start_date": self._env.config.base.start_date.strftime("%Y-%m-%d"), "start_date": self._env.config.base.start_date.strftime("%Y-%m-%d"), "end_date": self._env.config.base.end_date.strftime("%Y-%m-%d"), "last_end_time": self._env.config.base.end_date.strftime("%Y-%m-%d"), "last_run_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), } event_start_time = self._env.config.base.start_date persist_meta = self._recorder.load_meta() if persist_meta: # 不修改回测开始时间 self._env.config.base.start_date = persist_meta['start_date'] event_start_time = datetime.datetime.strptime( persist_meta['last_end_time'], '%Y-%m-%d').date() + datetime.timedelta(days=1) # 代表历史有运行过,根据历史上次运行的end_date下一天设为事件发送的start_time self._meta["origin_start_date"] = persist_meta["origin_start_date"] self._meta["start_date"] = persist_meta["start_date"] self._meta[ "last_end_time"] = self._env.config.base.end_date.strftime( "%Y-%m-%d") env.set_data_source( IncrementcalDataSource( self._env.config.base.data_bundle_path, getattr(self._env.config.base, "future_info", {}), self._env.config.base.start_date)) event_source = IncrementalEventSource(env, event_start_time, self._env.config.base.end_date) env.set_event_source(event_source)
def check_less_than(func_name, value): if value >= high: raise RQInvalidArgument( _(u"function {}: invalid {} argument, expect a value < {}, got {} (type: {})").format( func_name, self._arg_name, high, value, type(value) ))
def check_greater_than(func_name, value): if value <= low: raise RQInvalidArgument( _(u"function {}: invalid {} argument, expect a value > {}, got {} (type: {})").format( func_name, self._arg_name, low, value, type(value) ))
def order_value(id_or_ins, cash_amount, price=None, style=None): """ 使用想要花费的金钱买入/卖出股票,而不是买入/卖出想要的股数,正数代表买入,负数代表卖出。股票的股数总是会被调整成对应的100的倍数(在A中国A股市场1手是100股)。当您提交一个卖单时,该方法代表的意义是您希望通过卖出该股票套现的金额。如果金额超出了您所持有股票的价值,那么您将卖出所有股票。需要注意,如果资金不足,该API将不会创建发送订单。 :param id_or_ins: 下单标的物 :type id_or_ins: :class:`~Instrument` object | `str` :param float cash_amount: 需要花费现金购买/卖出证券的数目。正数代表买入,负数代表卖出。 :param float price: 下单价格,默认为None,表示 :class:`~MarketOrder`, 此参数主要用于简化 `style` 参数。 :param style: 下单类型, 默认是市价单。目前支持的订单类型有 :class:`~LimitOrder` 和 :class:`~MarketOrder` :type style: `OrderStyle` object :return: :class:`~Order` object :example: .. code-block:: python #买入价值¥10000的平安银行股票,并以市价单发送。如果现在平安银行股票的价格是¥7.5,那么下面的代码会买入1300股的平安银行,因为少于100股的数目将会被自动删除掉: order_value('000001.XSHE', 10000) #卖出价值¥10000的现在持有的平安银行: order_value('000001.XSHE', -10000) """ style = cal_style(price, style) if isinstance(style, LimitOrder): if style.get_limit_price() <= 0: raise RQInvalidArgument(_(u"Limit order price should be positive")) order_book_id = assure_stock_order_book_id(id_or_ins) env = Environment.get_instance() price = env.get_last_price(order_book_id) if not is_valid_price(price): user_system_log.warn( _(u"Order Creation Failed: [{order_book_id}] No market data"). format(order_book_id=order_book_id)) return account = env.portfolio.accounts[DEFAULT_ACCOUNT_TYPE.STOCK.name] round_lot = int(env.get_instrument(order_book_id).round_lot) if cash_amount > 0: cash_amount = min(cash_amount, account.cash) if isinstance(style, MarketOrder): amount = int( Decimal(cash_amount) / Decimal(price) / Decimal(round_lot)) * round_lot else: amount = int( Decimal(cash_amount) / Decimal(style.get_limit_price()) / Decimal(round_lot)) * round_lot # if the cash_amount is larger than you current security’s position, # then it will sell all shares of this security. position = account.positions[order_book_id] amount = downsize_amount(amount, position) return order_shares(order_book_id, amount, style=style)
def benchmark(self, value): user_system_log.warn( _(u"[abandon] {} is no longer used.").format('context.benchmark'))
def raise_instrument_error(self, func_name, value, instrument_info): raise RQInvalidArgument( _(u"function {}: invalid {} argument, expected a {}, got {} (type: {})" ).format(func_name, self._arg_name, instrument_info, value, type(value)))
def commission(self, value): user_system_log.warn( _(u"[abandon] {} is no longer used.").format('context.commission'))
def order_shares(id_or_ins, amount, price=None, style=None): """ 落指定股数的买/卖单,最常见的落单方式之一。如有需要落单类型当做一个参量传入,如果忽略掉落单类型,那么默认是市价单(market order)。 :param id_or_ins: 下单标的物 :type id_or_ins: :class:`~Instrument` object | `str` :param int amount: 下单量, 正数代表买入,负数代表卖出。将会根据一手xx股来向下调整到一手的倍数,比如中国A股就是调整成100股的倍数。 :param float price: 下单价格,默认为None,表示 :class:`~MarketOrder`, 此参数主要用于简化 `style` 参数。 :param style: 下单类型, 默认是市价单。目前支持的订单类型有 :class:`~LimitOrder` 和 :class:`~MarketOrder` :type style: `OrderStyle` object :return: :class:`~Order` object | None :example: .. code-block:: python #购买Buy 2000 股的平安银行股票,并以市价单发送: order_shares('000001.XSHE', 2000) #卖出2000股的平安银行股票,并以市价单发送: order_shares('000001.XSHE', -2000) #购买1000股的平安银行股票,并以限价单发送,价格为¥10: order_shares('000001.XSHG', 1000, style=LimitOrder(10)) """ if amount == 0: # 如果下单量为0,则认为其并没有发单,则直接返回None user_system_log.warn(_(u"Order Creation Failed: Order amount is 0.")) return None style = cal_style(price, style) if isinstance(style, LimitOrder): if style.get_limit_price() <= 0: raise RQInvalidArgument(_(u"Limit order price should be positive")) order_book_id = assure_stock_order_book_id(id_or_ins) env = Environment.get_instance() price = env.get_last_price(order_book_id) if not is_valid_price(price): user_system_log.warn( _(u"Order Creation Failed: [{order_book_id}] No market data"). format(order_book_id=order_book_id)) return if amount > 0: side = SIDE.BUY position_effect = POSITION_EFFECT.OPEN else: amount = abs(amount) side = SIDE.SELL position_effect = POSITION_EFFECT.CLOSE if side == SIDE.BUY: # 卖出不再限制 round_lot, order_shares 不再依赖 portfolio round_lot = int(env.get_instrument(order_book_id).round_lot) try: amount = int(Decimal(amount) / Decimal(round_lot)) * round_lot except ValueError: amount = 0 r_order = Order.__from_create__(order_book_id, amount, side, style, position_effect) if amount == 0: # 如果计算出来的下单量为0, 则不生成Order, 直接返回None # 因为很多策略会直接在handle_bar里面执行order_target_percent之类的函数,经常会出现下一个量为0的订单,如果这些订单都生成是没有意义的。 user_system_log.warn(_(u"Order Creation Failed: 0 order quantity")) return if r_order.type == ORDER_TYPE.MARKET: r_order.set_frozen_price(price) if env.can_submit_order(r_order): env.broker.submit_order(r_order) return r_order
def order_target_portfolio(target_portfolio): # type: (Dict[Union[str, Instrument], float]) -> List[Order] """ 买入/卖出证券以批量调整证券的仓位,以期使其持仓市值占账户总权益的比重达到指定值。 :param target_portfolio: 下单标的物及其目标市值占比的字典 :example: .. code-block:: python # 调整仓位,以使平安银行和万科 A 的持仓占比分别达到 10% 和 15% order_target_portfolio({ '000001.XSHE': 0.1 '000002.XSHE': 0.15 }) """ if isinstance(target_portfolio, pd.Series): # FIXME: kind of dirty total_percent = sum(target_portfolio) else: total_percent = sum(six.itervalues(target_portfolio)) if total_percent > 1: raise RQInvalidArgument(_(u"total percent should be lower than 1, current: {}").format(total_percent)) env = Environment.get_instance() account = env.portfolio.accounts[DEFAULT_ACCOUNT_TYPE.STOCK] account_value = account.total_value target_quantities = {} for id_or_ins, target_percent in target_portfolio.items(): order_book_id = assure_order_book_id(id_or_ins) if target_percent < 0: raise RQInvalidArgument(_(u"target percent of {} should between 0 and 1, current: {}").format( order_book_id, target_percent )) price = env.data_proxy.get_last_price(order_book_id) if not is_valid_price(price): user_system_log.warn( _(u"Order Creation Failed: [{order_book_id}] No market data").format(order_book_id=order_book_id) ) continue target_quantities[order_book_id] = account_value * target_percent / price close_orders, open_orders = [], [] current_quantities = { p.order_book_id: p.quantity for p in account.get_positions() if p.direction == POSITION_DIRECTION.LONG } for order_book_id, quantity in current_quantities.items(): if order_book_id not in target_portfolio: close_orders.append(Order.__from_create__( order_book_id, quantity, SIDE.SELL, MarketOrder(), POSITION_EFFECT.CLOSE )) round_lot = 100 for order_book_id, target_quantity in target_quantities.items(): if order_book_id in current_quantities: delta_quantity = target_quantity - current_quantities[order_book_id] else: delta_quantity = target_quantity if delta_quantity >= round_lot: delta_quantity = math.floor(delta_quantity / round_lot) * round_lot open_orders.append(Order.__from_create__( order_book_id, delta_quantity, SIDE.BUY, MarketOrder(), POSITION_EFFECT.OPEN )) elif delta_quantity < -1: delta_quantity = math.floor(delta_quantity) close_orders.append(Order.__from_create__( order_book_id, abs(delta_quantity), SIDE.SELL, MarketOrder(), POSITION_EFFECT.CLOSE )) submit_orders = [] for order in chain(close_orders, open_orders): if env.can_submit_order(order): submit_orders.append(order) env.broker.submit_order(order) return submit_orders
def total_trades(self): """abandon""" user_system_log.warn( _(u"[abandon] {} is no longer valid.").format( 'position.total_trades')) return 0
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() try: future_info = config.base.future_info except AttributeError: pass else: deep_update(future_info, future_info_cn.CN_FUTURE_INFO) if not env.data_source: env.set_data_source(BaseDataSource(config.base.data_bundle_path)) if env.price_board is None: from .core.bar_dict_price_board import BarDictPriceBoard env.price_board = BarDictPriceBoard() env.set_data_proxy(DataProxy(env.data_source, env.price_board)) 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) # 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 try: env.portfolio = broker.get_portfolio() except NotImplementedError: pass else: if env.benchmark_provider: env.benchmark_portfolio = BenchmarkPortfolio( env.benchmark_provider, env.portfolio.units) try: env.booking = broker.get_booking() except NotImplementedError: pass 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) 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) env.user_strategy = user_strategy 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): if isinstance(v, RqAttrDict): v = v.__dict__ 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) if env.portfolio: persist_helper.register('portfolio', env.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, open_auction): # type: (Account, Order, bool) -> None if order.position_effect == POSITION_EFFECT.EXERCISE: raise NotImplementedError order_book_id = order.order_book_id instrument = self._env.get_instrument(order_book_id) if open_auction: deal_price = self._open_auction_deal_price_decider( order_book_id, order.side) else: 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._env.trading_dt.date(): reason = _( u"Order Cancelled: current security [{order_book_id}] can not be traded" u" 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) return price_board = self._env.price_board if order.type == ORDER_TYPE.LIMIT: if order.side == SIDE.BUY and order.price < deal_price: return if order.side == SIDE.SELL and order.price > deal_price: return # 是否限制涨跌停不成交 if self._price_limit: if order.side == SIDE.BUY and deal_price >= price_board.get_limit_up( order_book_id): return if order.side == SIDE.SELL and deal_price <= price_board.get_limit_down( order_book_id): return if self._liquidity_limit: if order.side == SIDE.BUY and price_board.get_a1( order_book_id) == 0: return if order.side == SIDE.SELL and price_board.get_b1( order_book_id) == 0: return 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) return 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) return 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) return 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) return if self._volume_limit: if open_auction: volume = self._env.data_proxy.get_open_auction_bar( order_book_id, self._env.calendar_dt).volume else: volume = self._env.get_bar(order_book_id).volume if volume == volume: volume_limit = round( 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) return fill = min(order.unfilled_quantity, volume_limit) else: fill = order.unfilled_quantity else: fill = order.unfilled_quantity ct_amount = account.calc_close_today_amount(order_book_id, fill, order.position_direction) 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(trade) trade._tax = self._env.get_trade_tax(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 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) if np.isnan(price_board.get_last_price(order_book_id)): 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 deal_price = self._deal_price_decider(order_book_id, order.side) 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 else: if self._price_limit and 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 elif self._price_limit and 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._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._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.side, deal_price) trade = Trade.__from_create__( order_id=order.order_id, calendar_dt=self._calendar_dt, trading_dt=self._trading_dt, 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._commission_decider.get_commission( account.type, trade) trade._tax = self._tax_decider.get_tax(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)) 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 25 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) order.mark_cancelled(reason)
def get_position_model(self, account_type): if account_type not in self._position_model_dict: raise RuntimeError( _(u"Unknown Account Type {}").format(account_type)) return self._position_model_dict[account_type]
def raise_instrument_not_listed_error(self, func_name, value): return self.raise_instrument_error( func_name, value, _("listed order_book_id/instrument"))
def match(self, open_orders): for account, order in open_orders: bar = self._board[order.order_book_id] bar_status = bar._bar_status if bar_status == BAR_STATUS.ERROR: listed_date = bar.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 deal_price = self._deal_price_decider(bar) 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 else: if self._bar_limit and order.side == SIDE.BUY and bar_status == BAR_STATUS.LIMIT_UP: 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 elif self._bar_limit and order.side == SIDE.SELL and bar_status == BAR_STATUS.LIMIT_DOWN: 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._bar_limit: if order.side == SIDE.BUY and bar_status == BAR_STATUS.LIMIT_UP: continue if order.side == SIDE.SELL and bar_status == BAR_STATUS.LIMIT_DOWN: continue volume_limit = round( bar.volume * self._volume_percent) - self._turnover[order.order_book_id] round_lot = bar.instrument.round_lot volume_limit = (volume_limit // round_lot) * round_lot if volume_limit <= 0: if order.type == ORDER_TYPE.MARKET: reason = _( 'Order Cancelled: market order {order_book_id} volume {order_volume}' ' 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) 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.side, deal_price) trade = Trade.__from_create__( order_id=order.order_id, calendar_dt=self._calendar_dt, trading_dt=self._trading_dt, 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._commission_decider.get_commission( account.type, trade) trade._tax = self._tax_decider.get_tax(account.type, trade) order.fill(trade) self._turnover[order.order_book_id] += fill Environment.get_instance().event_bus.publish_event( Event(EVENT.TRADE, account=account, trade=trade)) if order.type == ORDER_TYPE.MARKET and order.unfilled_quantity != 0: reason = _( "Order Cancelled: market order {order_book_id} volume {order_volume} is" " larger than 25 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) order.mark_cancelled(reason)
def raise_not_valid_future_error(self, func_name, value): return self.raise_instrument_error( func_name, value, _("valid future order_book_id/instrument"))
def future_portfolio(self): user_system_log.warn( _(u"[abandon] {} is no longer used.").format( 'context.future_portfolio')) return self.future_account
def slippage(self, value): user_system_log.warn( _(u"[abandon] {} is no longer used.").format('context.slippage'))
def start_up(self, env, mod_config): env.set_price_board(StockLimitUpDownPriceBoard()) type_ = DataSourceType(mod_config.source) if type_ in [DataSourceType.MONGO, DataSourceType.REAL_TIME]: from rqalpha_mod_fxdayu_source.data_source.mongo import MongoDataSource, MongoCacheDataSource args = (env.config.base.data_bundle_path, mod_config.mongo_url) data_source_cls = MongoCacheDataSource if mod_config.enable_cache else MongoDataSource elif type_ == DataSourceType.BUNDLE: from rqalpha_mod_fxdayu_source.data_source.bundle import BundleCacheDataSource, BundleDataSource args = (env.config.base.data_bundle_path, mod_config.bundle_path) data_source_cls = BundleCacheDataSource if mod_config.enable_cache else BundleDataSource elif type_ == DataSourceType.QUANTOS: from rqalpha_mod_fxdayu_source.data_source.quantos import QuantOsSource, QuantOsCacheSource args = (env.config.base.data_bundle_path, mod_config.quantos_url, mod_config.quantos_user, mod_config.quantos_token) data_source_cls = QuantOsCacheSource if mod_config.enable_cache else QuantOsSource else: raise RuntimeError("data source type [%s] is not supported" % mod_config.source) if mod_config.enable_cache: if mod_config.cache_length: CacheMixin.set_cache_length(int(mod_config.cache_length)) if mod_config.max_cache_space: CacheMixin.set_max_cache_space(int(mod_config.max_cache_space)) data_source = data_source_cls(*args) mod_config.redis_uri = mod_config.redis_url # fit rqalpha if env.config.base.run_type is RUN_TYPE.BACKTEST and env.config.base.persist_mode == PERSIST_MODE.ON_NORMAL_EXIT: # generate user context using backtest persist_provider = DiskPersistProvider(mod_config.persist_path) env.set_persist_provider(persist_provider) is_real_time = env.config.base.run_type in (RUN_TYPE.PAPER_TRADING, RUN_TYPE.LIVE_TRADING) if is_real_time or type_ == DataSourceType.REAL_TIME: user_system_log.warn( _("[Warning] When you use this version of RealtimeTradeMod, history_bars can only " "get data from yesterday.")) if type_ == DataSourceType.QUANTOS: inday_bars = QuantOsIndayBars(mod_config.quantos_url, mod_config.quantos_user, mod_config.quantos_token) elif mod_config.redis_url: inday_bars = RedisIndayBars(mod_config.redis_url) system_log.info(_("RealtimeTradeMod using market from redis")) else: raise RuntimeError( "No Inday bar data source with valid config") data_source = RealtimeDataSource(inday_bars=inday_bars, hist_source=data_source) if is_real_time: event_source = RealTimeEventSource(mod_config.fps, mod_config) # add persist persist_provider = DiskPersistProvider(mod_config.persist_path) env.set_persist_provider(persist_provider) env.config.base.persist = True env.config.base.persist_mode = PERSIST_MODE.REAL_TIME else: event_source = IntervalEventSource(env) env.set_data_source(data_source) # a patch to start_date allowed it to be setted large than last trade date. if env.config.base.start_date == datetime.now().date(): trading_dates = data_source.get_trading_calendar() pos = trading_dates.searchsorted(env.config.base.start_date) if trading_dates[pos].to_pydatetime().date( ) != env.config.base.start_date: env.config.base.start_date = trading_dates[max( 0, pos - 1)].to_pydatetime().date() env.set_event_source(event_source)
def check_is_instance_of(func_name, value): if not isinstance(value, types): raise RQInvalidArgument( _(u"function {}: invalid {} argument, expect a value of type {}, got {} (type: {})").format( func_name, self._arg_name, types, value, type(value) ))
def raise_not_valid_future_error(self, func_name, arg_name, value): raise RQInvalidArgument( _(u"function {}: invalid {} argument, expect a valid future instrument/order_book_id/symbol, " u"got {} (type: {})").format( func_name, self._arg_name, value, type(value) ))