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 _validate_benchmark(config, data_proxy): benchmark = config.base.benchmark if benchmark is None: return instrument = data_proxy.instruments(benchmark) if instrument is None: raise patch_user_exc( ValueError(_(u"invalid benchmark {}").format(benchmark))) if instrument.order_book_id == "000300.XSHG": # 000300.XSHG 数据进行了补齐,因此认为只要benchmark设置了000300.XSHG,就存在数据,不受限于上市日期。 return config = Environment.get_instance().config start_date = config.base.start_date end_date = config.base.end_date if instrument.listed_date.date() > start_date: raise patch_user_exc( ValueError( _(u"benchmark {benchmark} has not been listed on {start_date}" ).format(benchmark=benchmark, start_date=start_date))) if instrument.de_listed_date.date() < end_date: raise patch_user_exc( ValueError( _(u"benchmark {benchmark} has been de_listed on {end_date}"). format(benchmark=benchmark, end_date=end_date)))
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 account = account_model(starting_cash, positions) units += account.total_value accounts[account_type] = account return Portfolio(config.base.start_date, 1, units, accounts)
def tear_down(self, *args): result = {} for mod_name, __ in reversed(self._mod_list): try: system_log.debug( _(u"mod tear_down [START] {}").format(mod_name)) ret = self._mod_dict[mod_name].tear_down(*args) 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 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 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 register_account_type(self, account_type, value): for k, v in six.iteritems(self.account_type_dict): if v == value: raise RuntimeError( _(u"value {value} has been used for {original_key}"). format(value=value, original_key=k)) self.account_type_dict[account_type] = value
def _is_number(self, func_name, value): try: v = float(value) except ValueError: raise RQInvalidArgument( _(u"function {}: invalid {} argument, expect a number, got {} (type: {})" ).format(func_name, self._arg_name, value, type(value)))
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 = "bwtougu.mod.bwtougu_mod_" + mod_name else: lib_name = "bwtougu_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 wrapper(*args, **kwargs): if not Environment.get_instance().config.extra.is_hold: return func(*args, **kwargs) else: system_log.debug( _(u"not run {}({}, {}) because strategy is hold").format( func, args, kwargs))
def __getitem__(self, key): if not isinstance(key, six.string_types): raise patch_user_exc( ValueError( 'invalid key {} (use order_book_id please)'.format(key))) instrument = self._data_proxy.instruments(key) if instrument is None: raise patch_user_exc( ValueError('invalid order book id or symbol: {}'.format(key))) order_book_id = instrument.order_book_id try: return self._cache[order_book_id] except KeyError: try: bar = self._data_proxy.get_bar(order_book_id, self._dt, self._frequency) except Exception as e: system_log.exception(e) raise patch_user_exc( KeyError( _(u"id_or_symbols {} does not exist").format(key))) if bar is None: return BarObject(instrument, NANDict, self._dt) else: self._cache[order_book_id] = bar return bar
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 _are_valid_query_entities(self, func_name, entities): from sqlalchemy.orm.attributes import InstrumentedAttribute for e in entities: if not isinstance(e, InstrumentedAttribute): raise RQInvalidArgument( _(u"function {}: invalid {} argument, should be entity like " u"Fundamentals.balance_sheet.total_equity, got {} (type: {})" ).format(func_name, self.arg_name, e, type(e)))
def wrapper(*args, **kwargs): phase = cls.stack.top.phase if phase not in phases: raise patch_user_exc( RuntimeError( _(u"You cannot call %s when executing %s") % (func.__name__, phase.value))) return func(*args, **kwargs)
def check_is_valid_date(func_name, value): if ignore_none and value is None: return None if isinstance(value, (datetime.date, pd.Timestamp)): return if isinstance(value, six.string_types): try: v = parse_date(value) return except ValueError: raise RQInvalidArgument( _(u"function {}: invalid {} argument, expect a valid date, got {} (type: {})" ).format(func_name, self._arg_name, value, type(value))) raise RQInvalidArgument( _(u"function {}: invalid {} argument, expect a valid date, got {} (type: {})" ).format(func_name, self._arg_name, value, type(value)))
def get_instance(cls): """ 返回已经创建的 Environment 对象 """ if Environment._env is None: raise RuntimeError( _(u"Environment has not been created. Please Use `Environment.get_instance()` after RQAlpha init" )) return Environment._env
def check_is_in(func_name, value): if ignore_none and value is None: return if value not in valid_values: raise RQInvalidArgument( _(u"function {}: invalid {} argument, valid: {}, got {} (type: {})" ).format(func_name, self._arg_name, repr(valid_values), value, type(value)))
def start_up(self, env, mod_config): if env.config.base.run_type == RUN_TYPE.LIVE_TRADING: return 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.set_event_source(event_source)
def _get_universe(self): universe = self._env.get_universe() if len( universe ) == 0 and DEFAULT_ACCOUNT_TYPE.STOCK.name not in self._config.base.accounts: raise patch_user_exc(RuntimeError( _("Current universe is empty. Please use subscribe function before trade" )), force=True) return universe
def assure_order_book_id(id_or_ins): if isinstance(id_or_ins, Instrument): order_book_id = id_or_ins.order_book_id elif isinstance(id_or_ins, six.string_types): order_book_id = Environment.get_instance().data_proxy.instruments( id_or_ins).order_book_id else: raise RQInvalidArgument(_(u"unsupported order_book_id type")) return order_book_id
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 parse_run_type(rt_str): assert isinstance(rt_str, six.string_types) mapping = { "b": RUN_TYPE.BACKTEST, "p": RUN_TYPE.PAPER_TRADING, "r": RUN_TYPE.LIVE_TRADING, } try: return mapping[rt_str] except KeyError: raise RuntimeError(_(u"unknown run type: {}").format(rt_str))
def _sell_all_stock(order_book_id, amount, style): env = Environment.get_instance() order = Order.__from_create__(order_book_id, amount, SIDE.SELL, style, None) if amount == 0: order.mark_rejected(_(u"Order Creation Failed: 0 order quantity")) return order if env.can_submit_order(order): env.broker.submit_order(order) return order
def cancel_order(order): """ 撤单 :param order: 需要撤销的order对象 :type order: :class:`~Order` object """ if order is None: patch_user_exc(KeyError(_(u"Cancel order fail: invalid order id"))) env = Environment.get_instance() if env.can_cancel_order(order): env.broker.cancel_order(order) return order
def _is_valid_frequency(self, func_name, value): valid = isinstance(value, six.string_types) and value[-1] in ("d", "m") if valid: try: valid = int(value[:-1]) > 0 except ValueError: valid = False if not valid: raise RQInvalidArgument( _(u"function {}: invalid {} argument, frequency should be in form of " u"'1m', '5m', '1d', got {} (type: {})").format( func_name, self.arg_name, value, type(value)))
def parse_accounts(accounts): a = {} if isinstance(accounts, tuple): accounts = {account_type: starting_cash for account_type, starting_cash in accounts} for account_type, starting_cash in six.iteritems(accounts): if starting_cash is None: continue starting_cash = float(starting_cash) a[account_type.upper()] = starting_cash if len(a) == 0: raise RuntimeError(_(u"None account type has been selected.")) return a
def _are_valid_instruments(self, func_name, values): if isinstance(values, (six.string_types, Instrument)): self._is_valid_instrument(func_name, values) return if isinstance(values, list): for v in values: self._is_valid_instrument(func_name, v) return raise RQInvalidArgument( _(u"function {}: invalid {} argument, expect a string or a list of string, got {} (type: {})" ).format(func_name, self._arg_name, repr(values), type(values)))
def order_target_percent(id_or_ins, percent, price=None, style=None): """ 买入/卖出证券以自动调整该证券的仓位到占有一个指定的投资组合的目标百分比。 * 如果投资组合中没有任何该证券的仓位,那么会买入等于现在投资组合总价值的目标百分比的数目的证券。 * 如果投资组合中已经拥有该证券的仓位,那么会买入/卖出目标百分比和现有百分比的差额数目的证券,最终调整该证券的仓位占据投资组合的比例至目标百分比。 其实我们需要计算一个position_to_adjust (即应该调整的仓位) `position_to_adjust = target_position - current_position` 投资组合价值等于所有已有仓位的价值和剩余现金的总和。买/卖单会被下舍入一手股数(A股是100的倍数)的倍数。目标百分比应该是一个小数,并且最大值应该<=1,比如0.5表示50%。 如果position_to_adjust 计算之后是正的,那么会买入该证券,否则会卖出该证券。 需要注意,如果资金不足,该API将不会创建发送订单。 :param id_or_ins: 下单标的物 :type id_or_ins: :class:`~Instrument` object | `str` | List[:class:`~Instrument`] | List[`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 #如果投资组合中已经有了平安银行股票的仓位,并且占据目前投资组合的10%的价值,那么以下代码会买入平安银行股票最终使其占据投资组合价值的15%: order_target_percent('000001.XSHE', 0.15) """ if percent < 0 or percent > 1: raise RQInvalidArgument(_(u"percent should between 0 and 1")) order_book_id = assure_stock_order_book_id(id_or_ins) style = cal_style(price, style) account = Environment.get_instance().portfolio.accounts[ DEFAULT_ACCOUNT_TYPE.STOCK.name] position = account.positions[order_book_id] if percent == 0: return _sell_all_stock(order_book_id, position.sellable, style) return order_value(order_book_id, account.total_value * percent - position.market_value, style=style)
def _is_valid_interval(self, func_name, value): valid = isinstance( value, six.string_types) and value[-1] in {'d', 'm', 'q', 'y'} if valid: try: valid = int(value[:-1]) > 0 except ValueError: valid = False if not valid: raise RQInvalidArgument( _(u"function {}: invalid {} argument, interval should be in form of '1d', '3m', '4q', '2y', " u"got {} (type: {})").format(func_name, self.arg_name, value, type(value)))
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) 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: system_log.warn( _(u"[deprecated] before_day_trading is no longer used. use before_trading instead." )) if self._before_night_trading is not None: system_log.warn( _(u"[deprecated] before_night_trading is no longer used. use before_trading instead." ))