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): """ 通用下单函数,策略可以通过该函数自由选择参数下单。 :param id_or_ins: 下单标的物 :type id_or_ins: :class:`~Instrument` object | `str` :param float amount: 下单量,需为正数 :param side: 多空方向,多(SIDE.BUY)或空(SIDE.SELL) :type side: :class:`~SIDE` enum :param float price: 下单价格,默认为None,表示市价单 :param position_effect: 开平方向,开仓(POSITION_EFFECT.OPEN),平仓(POSITION.CLOSE)或平今(POSITION_EFFECT.CLOSE_TODAY),交易股票不需要该参数 :type position_effect: :class:`~POSITION_EFFECT` enum :return: :class:`~Order` object | None :example: .. code-block:: python # 购买 2000 股的平安银行股票,并以市价单发送: submit_order('000001.XSHE', 2000, SIDE.BUY) # 平 10 份 RB1812 多方向的今仓,并以 4000 的价格发送限价单 submit_order('RB1812', 10, SIDE.SELL, price=4000, position_effect=POSITION_EFFECT.CLOSE_TODAY) """ 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 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( Environment.get_instance().data_proxy.instruments(id_or_symbols)) else: raise RQInvalidArgument(_(u"unsupported order_book_id type"))
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 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 | None :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 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 | None :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] if cash_amount > 0: cash_amount = min(cash_amount, account.cash) if isinstance(style, MarketOrder): amount = int(Decimal(cash_amount) / Decimal(price)) else: amount = int(Decimal(cash_amount) / Decimal(style.get_limit_price())) # 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 _is_number(self, func_name, value): try: v = float(value) except (ValueError, TypeError): raise RQInvalidArgument( _(u"function {}: invalid {} argument, expect a number, got {} (type: {})").format( func_name, self._arg_name, 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 _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_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 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 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 cal_style(price, style): if price is None and style is None: return MarketOrder() if style is not None: if not isinstance(style, OrderStyle): raise RuntimeError return style if isinstance(price, OrderStyle): # 为了 order_xxx('RB1710', 10, MarketOrder()) 这种写法 if isinstance(price, LimitOrder): if np.isnan(price.get_limit_price()): raise RQInvalidArgument(_(u"Limit order price should not be nan.")) return price if np.isnan(price): raise RQInvalidArgument(_(u"Limit order price should not be nan.")) return LimitOrder(price)
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 | None :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) try: market_value = position.market_value except RuntimeError: order_result = order_value(order_book_id, np.nan, style=style) if order_result: raise else: return order_value(order_book_id, account.total_value * percent - 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, TypeError): 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 _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 _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, TypeError): 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 _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]) <= 2099 and 1 <= int(value[-1]) <= 4 except (ValueError, TypeError): 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 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_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 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] not in {'m', 'd'}: raise RQInvalidArgument('invalid frequency {}'.format(frequency)) if frequency[-1] == 'm' and env.config.base.frequency == '1d': raise RQInvalidArgument('can not get minute history in day back test') if frequency[-1] == 'd' and frequency != '1d': raise RQInvalidArgument('invalid frequency') 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 and ExecutionContext.phase() != EXECUTION_PHASE.AFTER_TRADING ) 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_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) ))
def order(id_or_ins, amount, side, position_effect, style): if not isinstance(style, OrderStyle): raise RuntimeError if amount < 0: raise RuntimeError 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")) order_book_id = assure_future_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." )) 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 amount = int(amount) position = Environment.get_instance().portfolio.positions[order_book_id] orders = [] if position_effect == POSITION_EFFECT.CLOSE: if side == SIDE.BUY: # 如果平仓量大于持仓量,则 Warning 并 取消订单创建 if amount > position.sell_quantity: user_system_log.warn( _(u"Order Creation Failed: close amount {amount} is larger than position " u"quantity {quantity}").format( amount=amount, quantity=position.sell_quantity)) return [] sell_old_quantity = position.sell_old_quantity if amount > sell_old_quantity: if sell_old_quantity != 0: # 如果有昨仓,则创建一个 POSITION_EFFECT.CLOSE 的平仓单 orders.append( Order.__from_create__(order_book_id, sell_old_quantity, side, style, POSITION_EFFECT.CLOSE)) # 剩下还有仓位,则创建一个 POSITION_EFFECT.CLOSE_TODAY 的平今单 orders.append( Order.__from_create__(order_book_id, amount - sell_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)) else: if amount > position.buy_quantity: user_system_log.warn( _(u"Order Creation Failed: close amount {amount} is larger than position " u"quantity {quantity}").format( amount=amount, quantity=position.buy_quantity)) return [] buy_old_quantity = position.buy_old_quantity if amount > buy_old_quantity: if buy_old_quantity != 0: orders.append( Order.__from_create__(order_book_id, buy_old_quantity, side, style, POSITION_EFFECT.CLOSE)) orders.append( Order.__from_create__(order_book_id, amount - buy_old_quantity, side, style, POSITION_EFFECT.CLOSE_TODAY)) else: orders.append( Order.__from_create__(order_book_id, amount, side, style, POSITION_EFFECT.CLOSE)) else: orders.append( Order.__from_create__(order_book_id, amount, side, style, position_effect)) 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 [] 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 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 round_lot = int(env.get_instrument(order_book_id).round_lot) if side == SIDE.BUY or amount != env.portfolio.accounts[ DEFAULT_ACCOUNT_TYPE.STOCK.name ].positions[order_book_id].sellable: # 一次性申报卖出时可以卖散股 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