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 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 _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 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 _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 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 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 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 to_date(date): if isinstance(date, six.string_types): return parse(date).date() if isinstance(date, datetime.date): try: return date.date() except AttributeError: return date raise RQInvalidArgument('unknown date value: {}'.format(date))
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 _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 _is_valid_quarter(self, func_name, value): if value is None: valid = True else: valid = isinstance(value, six.string_types) and value[-2] == 'q' if valid: try: valid = 1990 <= int(value[:-2]) <= 2050 and 1 <= int( value[-1]) <= 4 except ValueError: valid = False if not valid: raise RQInvalidArgument( _(u"function {}: invalid {} argument, quarter should be in form of '2012q3', " u"got {} (type: {})").format(func_name, self.arg_name, value, type(value)))
def get_dividend(order_book_id, start_date, *args, **kwargs): # adjusted 参数在不复权数据回测时不再提供 env = Environment.get_instance() dt = env.trading_dt.date() - datetime.timedelta(days=1) start_date = to_date(start_date) if start_date > dt: raise RQInvalidArgument( _(u"in get_dividend, start_date {} is later than the previous test day {}" ).format(start_date, dt)) order_book_id = assure_order_book_id(order_book_id) array = env.data_proxy.get_dividend(order_book_id) if array is None: return None sd = start_date.year * 10000 + start_date.month * 100 + start_date.day ed = dt.year * 10000 + dt.month * 100 + dt.day return array[(array['announcement_date'] >= sd) & (array['announcement_date'] <= ed)]
def get_yield_curve(date=None, tenor=None): """ 获取某个国家市场指定日期的收益率曲线水平。 数据为2002年至今的中债国债收益率曲线,来源于中央国债登记结算有限责任公司。 :param date: 查询日期,默认为策略当前日期前一天 :type date: `str` | `date` | `datetime` | `pandas.Timestamp` :param str tenor: 标准期限,'0S' - 隔夜,'1M' - 1个月,'1Y' - 1年,默认为全部期限 :return: `pandas.DataFrame` - 查询时间段内无风险收益率曲线 :example: .. code-block:: python3 :linenos: [In] get_yield_curve('20130104') [Out] 0S 1M 2M 3M 6M 9M 1Y 2Y \ 2013-01-04 0.0196 0.0253 0.0288 0.0279 0.0280 0.0283 0.0292 0.0310 3Y 4Y ... 6Y 7Y 8Y 9Y 10Y \ 2013-01-04 0.0314 0.0318 ... 0.0342 0.0350 0.0353 0.0357 0.0361 ... """ env = Environment.get_instance() trading_date = env.trading_dt.date() yesterday = env.data_proxy.get_previous_trading_date(trading_date) if date is None: date = yesterday else: date = pd.Timestamp(date) if date > yesterday: raise RQInvalidArgument('get_yield_curve: {} >= now({})'.format( date, yesterday)) return env.data_proxy.get_yield_curve(start_date=date, end_date=date, tenor=tenor)
def unsubscribe(id_or_symbols): """ 取消订阅合约行情。取消订阅会导致合约池内合约的减少,如果当前合约池中没有任何合约,则策略直接退出。 :param id_or_ins: 标的物 :type id_or_ins: :class:`~Instrument` object | `str` | List[:class:`~Instrument`] | List[`str`] """ current_universe = Environment.get_instance().get_universe() if isinstance(id_or_symbols, six.string_types): order_book_id = instruments(id_or_symbols).order_book_id current_universe.discard(order_book_id) elif isinstance(id_or_symbols, Instrument): current_universe.discard(id_or_symbols.order_book_id) elif isinstance(id_or_symbols, Iterable): for item in id_or_symbols: i = assure_order_book_id(item) current_universe.discard(i) else: raise RQInvalidArgument(_(u"unsupported order_book_id type")) Environment.get_instance().update_universe(current_universe)
def subscribe(id_or_symbols): """ 订阅合约行情。该操作会导致合约池内合约的增加,从而影响handle_bar中处理bar数据的数量。 需要注意,用户在初次编写策略时候需要首先订阅合约行情,否则handle_bar不会被触发。 :param id_or_ins: 标的物 :type id_or_ins: :class:`~Instrument` object | `str` | List[:class:`~Instrument`] | List[`str`] """ current_universe = Environment.get_instance().get_universe() if isinstance(id_or_symbols, six.string_types): order_book_id = instruments(id_or_symbols).order_book_id current_universe.add(order_book_id) elif isinstance(id_or_symbols, Instrument): current_universe.add(id_or_symbols.order_book_id) elif isinstance(id_or_symbols, Iterable): for item in id_or_symbols: current_universe.add(assure_order_book_id(item)) else: raise RQInvalidArgument(_(u"unsupported order_book_id type")) verify_that('id_or_symbols')._are_valid_instruments( "subscribe", id_or_symbols) Environment.get_instance().update_universe(current_universe)
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): system_log.warn( _(u"Order Creation Failed: [{order_book_id}] No market data"). format(order_book_id=order_book_id)) return if price == 0: return order_shares(order_book_id, 0, style) 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 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 :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 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): 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 else: amount = abs(amount) side = SIDE.SELL 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, None) if price == 0: system_log.warn( _(u"Order Creation Failed: [{order_book_id}] No market data"). format(order_book_id=order_book_id)) r_order.mark_rejected( _(u"Order Creation Failed: [{order_book_id}] No market data"). format(order_book_id=order_book_id)) return r_order if amount == 0: # 如果计算出来的下单量为0, 则不生成Order, 直接返回None # 因为很多策略会直接在handle_bar里面执行order_target_percent之类的函数,经常会出现下一个量为0的订单,如果这些订单都生成是没有意义的。 r_order.mark_rejected(_(u"Order Creation Failed: 0 order quantity")) return r_order 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 history_bars(order_book_id, bar_count, frequency, fields=None, skip_suspended=True, include_now=False, adjust_type='pre'): """ 获取指定合约的历史行情,同时支持日以及分钟历史数据。不能在init中调用。 注意,该API会自动跳过停牌数据。 日回测获取分钟历史数据:不支持 日回测获取日历史数据 ========================= =================================================== 调用时间 返回数据 ========================= =================================================== T日before_trading T-1日day bar T日handle_bar T日day bar ========================= =================================================== 分钟回测获取日历史数据 ========================= =================================================== 调用时间 返回数据 ========================= =================================================== T日before_trading T-1日day bar T日handle_bar T-1日day bar ========================= =================================================== 分钟回测获取分钟历史数据 ========================= =================================================== 调用时间 返回数据 ========================= =================================================== T日before_trading T-1日最后一个minute bar T日handle_bar T日当前minute bar ========================= =================================================== :param order_book_id: 合约代码 :type order_book_id: `str` :param int bar_count: 获取的历史数据数量,必填项 :param str frequency: 获取数据什么样的频率进行。'1d'或'1m'分别表示每日和每分钟,必填项 :param str fields: 返回数据字段。必填项。见下方列表。 ========================= =================================================== fields 字段名 ========================= =================================================== datetime 时间戳 open 开盘价 high 最高价 low 最低价 close 收盘价 volume 成交量 total_turnover 成交额 open_interest 持仓量(期货专用) basis_spread 期现差(股指期货专用) settlement 结算价(期货日线专用) prev_settlement 结算价(期货日线专用) ========================= =================================================== :param bool skip_suspended: 是否跳过停牌数据 :param bool include_now: 是否包含当前数据 :param str adjust_type: 复权类型,默认为前复权 pre;可选 pre, none, post :return: `ndarray`, 方便直接与talib等计算库对接,效率较history返回的DataFrame更高。 :example: 获取最近5天的日线收盘价序列(策略当前日期为20160706): .. code-block:: python3 :linenos: [In] logger.info(history_bars('000002.XSHE', 5, '1d', 'close')) [Out] [ 8.69 8.7 8.71 8.81 8.81] """ order_book_id = assure_order_book_id(order_book_id) env = Environment.get_instance() dt = env.calendar_dt if frequency[-1] == 'm' and env.config.base.frequency == '1d': raise RQInvalidArgument('can not get minute history in day back test') if adjust_type not in {'pre', 'post', 'none'}: raise RuntimeError('invalid adjust_type') if frequency == '1d': sys_frequency = Environment.get_instance().config.base.frequency if ((sys_frequency in ['1m', 'tick'] and not include_now) or ExecutionContext.phase() == EXECUTION_PHASE.BEFORE_TRADING): dt = env.data_proxy.get_previous_trading_date( env.trading_dt.date()) # 当 EXECUTION_PHASE.BEFORE_TRADING 的时候,强制 include_now 为 False include_now = False if sys_frequency == "1d": # 日回测不支持 include_now include_now = False if fields is None: fields = ["datetime", "open", "high", "low", "close", "volume"] return env.data_proxy.history_bars(order_book_id, bar_count, frequency, fields, dt, skip_suspended=skip_suspended, include_now=include_now, adjust_type=adjust_type, adjust_orig=env.trading_dt)
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 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)))
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_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)))