def _handle_dividend_book_closure(self, trading_date): for order_book_id, position in six.iteritems(self._positions): if position.quantity == 0: continue dividend = Environment.get_instance( ).data_proxy.get_dividend_by_book_date(order_book_id, trading_date) if dividend is None: continue dividend_per_share = dividend[ 'dividend_cash_before_tax'] / dividend['round_lot'] position.dividend_(dividend_per_share) config = Environment.get_instance().config if config.extra.dividend_reinvestment: last_price = Environment.get_instance().data_proxy.get_bar( order_book_id, trading_date).close shares = position.quantity * dividend_per_share / last_price position._quantity += shares else: self._dividend_receivable[order_book_id] = { 'quantity': position.quantity, 'dividend_per_share': dividend_per_share, 'payable_date': self._int_to_date(dividend['payable_date']), }
def _before_trading(self, event): trading_date = Environment.get_instance().trading_dt.date() last_date = Environment.get_instance( ).data_proxy.get_previous_trading_date(trading_date) self._handle_dividend_book_closure(last_date) self._handle_dividend_payable(trading_date) self._handle_split(trading_date)
def plot(series_name, value): """ Add a point to custom series. :param str series_name: the name of custom series :param float value: the value of the series in this time :return: None """ Environment.get_instance().add_plot(series_name, value)
def is_de_listed(self): """ 判断合约是否过期 """ instrument = Environment.get_instance().get_instrument( self._order_book_id) current_date = Environment.get_instance().trading_dt if instrument.de_listed_date is not None and current_date >= instrument.de_listed_date: return True return False
def init(self): if not self._init: return with ExecutionContext(EXECUTION_PHASE.ON_INIT): with ModifyExceptionFromType(EXC_TYPE.USER_EXC): self._init(self._user_context) Environment.get_instance().event_bus.publish_event( Event(EVENT.POST_USER_INIT))
def update_universe(id_or_symbols): """ 该方法用于更新现在关注的证券的集合(e.g.:股票池)。PS:会在下一个bar事件触发时候产生(新的关注的股票池更新)效果。并且update_universe会是覆盖(overwrite)的操作而不是在已有的股票池的基础上进行增量添加。比如已有的股票池为['000001.XSHE', '000024.XSHE']然后调用了update_universe(['000030.XSHE'])之后,股票池就会变成000030.XSHE一个股票了,随后的数据更新也只会跟踪000030.XSHE这一个股票了。 :param id_or_ins: 标的物 :type id_or_ins: :class:`~Instrument` object | `str` | List[:class:`~Instrument`] | List[`str`] """ if isinstance(id_or_symbols, (six.string_types, Instrument)): id_or_symbols = [id_or_symbols] order_book_ids = set( assure_order_book_id(order_book_id) for order_book_id in id_or_symbols) if order_book_ids != Environment.get_instance().get_universe(): Environment.get_instance().update_universe(order_book_ids)
def is_suspended(order_book_id, count=1): """ 判断某只股票是否全天停牌。 :param str order_book_id: 某只股票的代码或股票代码,可传入单只股票的order_book_id, symbol :param int count: 回溯获取的数据个数。默认为当前能够获取到的最近的数据 :return: count为1时 `bool`; count>1时 `pandas.DataFrame` """ dt = Environment.get_instance().calendar_dt.date() order_book_id = assure_stock_order_book_id(order_book_id) return Environment.get_instance().data_proxy.is_suspended( order_book_id, dt, count)
def prev_close(self): """ [float] 截止到当前的最低价 """ try: return self._data['prev_close'] except (ValueError, KeyError): pass if self._prev_close is None: trading_dt = Environment.get_instance().trading_dt data_proxy = Environment.get_instance().data_proxy self._prev_close = data_proxy.get_prev_close( self._instrument.order_book_id, trading_dt) return self._prev_close
def prev_settlement(self): """ [float] 昨日结算价(期货专用) """ try: return self._data['prev_settlement'] except (ValueError, KeyError): pass if self._prev_settlement is None: trading_dt = Environment.get_instance().trading_dt data_proxy = Environment.get_instance().data_proxy self._prev_settlement = data_proxy.get_prev_settlement( self._instrument.order_book_id, trading_dt) return self._prev_settlement
def register_event(self): """ 注册事件 """ event_bus = Environment.get_instance().event_bus event_bus.prepend_listener(EVENT.PRE_BEFORE_TRADING, self._pre_before_trading)
def suspended(self): if self.isnan: return True return Environment.get_instance().data_proxy.is_suspended( self._instrument.order_book_id, int(self._data['datetime'] // 1000000))
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 order_lots(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 #买入20手的平安银行股票,并且发送市价单: order_lots('000001.XSHE', 20) #买入10手平安银行股票,并且发送限价单,价格为¥10: order_lots('000001.XSHE', 10, style=LimitOrder(10)) """ order_book_id = assure_stock_order_book_id(id_or_ins) round_lot = int( Environment.get_instance().get_instrument(order_book_id).round_lot) style = cal_style(price, style) return order_shares(id_or_ins, amount * round_lot, style=style)
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 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 get_commission(self, trade): order_book_id = trade.order_book_id env = Environment.get_instance() info = env.data_proxy.get_commission_info(order_book_id) commission = 0 if info['commission_type'] == COMMISSION_TYPE.BY_MONEY: contract_multiplier = env.get_instrument( trade.order_book_id).contract_multiplier if trade.position_effect == POSITION_EFFECT.OPEN: commission += trade.last_price * trade.last_quantity * contract_multiplier * info[ 'open_commission_ratio'] else: commission += trade.last_price * ( trade.last_quantity - trade.close_today_amount ) * contract_multiplier * info['close_commission_ratio'] commission += trade.last_price * trade.close_today_amount * contract_multiplier * info[ 'close_commission_today_ratio'] else: if trade.position_effect == POSITION_EFFECT.OPEN: commission += trade.last_quantity * info[ 'open_commission_ratio'] else: commission += (trade.last_quantity - trade.close_today_amount ) * info['close_commission_ratio'] commission += trade.close_today_amount * info[ 'close_commission_today_ratio'] return commission * self.multiplier
def order_target_value(id_or_ins, cash_amount, price=None, style=None): """ 买入/卖出并且自动调整该证券的仓位到一个目标价值。如果还没有任何该证券的仓位,那么会买入全部目标价值的证券。如果已经有了该证券的仓位,则会买入/卖出调整该证券的现在仓位和目标仓位的价值差值的数目的证券。需要注意,如果资金不足,该API将不会创建发送订单。 :param id_or_ins: 下单标的物 :type id_or_ins: :class:`~Instrument` object | `str` | List[:class:`~Instrument`] | List[`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 #如果现在的投资组合中持有价值¥3000的平安银行股票的仓位并且设置其目标价值为¥10000,以下代码范例会发送价值¥7000的平安银行的买单到市场。(向下调整到最接近每手股数即100的倍数的股数): order_target_value('000001.XSHE', 10000) """ order_book_id = assure_stock_order_book_id(id_or_ins) account = Environment.get_instance().portfolio.accounts[ DEFAULT_ACCOUNT_TYPE.STOCK.name] position = account.positions[order_book_id] style = cal_style(price, style) if cash_amount == 0: return _sell_all_stock(order_book_id, position.sellable, style) return order_value(order_book_id, cash_amount - position.market_value, style=style)
def is_st_stock(order_book_id, count=1): """ 判断股票在一段时间内是否为ST股(包括ST与*ST)。 ST股是有退市风险因此风险比较大的股票,很多时候您也会希望判断自己使用的股票是否是'ST'股来避开这些风险大的股票。另外,我们目前的策略比赛也禁止了使用'ST'股。 :param str order_book_id: 某只股票的代码,可传入单只股票的order_book_id, symbol :param int count: 回溯获取的数据个数。默认为当前能够获取到的最近的数据 :return: count为1时 `bool`; count>1时 `pandas.DataFrame` """ dt = Environment.get_instance().calendar_dt.date() order_book_id = assure_stock_order_book_id(order_book_id) return Environment.get_instance().data_proxy.is_st_stock( order_book_id, dt, count)
def annualized_returns(self): """ [float] 累计年化收益率 """ current_date = Environment.get_instance().trading_dt.date() return self.unit_net_value**(DAYS_CNT.DAYS_A_YEAR / float( (current_date - self.start_date).days + 1)) - 1
def industry(code): if not isinstance(code, six.string_types): code = code.code else: code = to_industry_code(code) return Environment.get_instance().data_proxy.industry(code)
def current_snapshot(id_or_symbol): """ 获得当前市场快照数据。只能在日内交易阶段调用,获取当日调用时点的市场快照数据。市场快照数据记录了每日从开盘到当前的数据信息,可以理解为一个动态的day bar数据。在目前分钟回测中,快照数据为当日所有分钟线累积而成,一般情况下,最后一个分钟线获取到的快照数据应当与当日的日线行情保持一致。需要注意,在实盘模拟中,该函数返回的是调用当时的市场快照情况,所以在同一个handle_bar中不同时点调用可能返回的数据不同。如果当日截止到调用时候对应股票没有任何成交,那么snapshot中的close, high, low, last几个价格水平都将以0表示。 :param str order_book_id: 合约代码或简称 :return: :class:`~Snapshot` :example: 在handle_bar中调用该函数,假设策略当前时间是20160104 09:33: .. code-block:: python3 :linenos: [In] logger.info(current_snapshot('000001.XSHE')) [Out] 2016-01-04 09:33:00.00 INFO Snapshot(order_book_id: '000001.XSHE', datetime: datetime.datetime(2016, 1, 4, 9, 33), open: 10.0, high: 10.025, low: 9.9667, last: 9.9917, volume: 2050320, total_turnover: 20485195, prev_close: 9.99) """ env = Environment.get_instance() frequency = env.config.base.frequency order_book_id = assure_order_book_id(id_or_symbol) return env.data_proxy.current_snapshot(order_book_id, frequency, env.calendar_dt)
def sector(code): if not isinstance(code, six.string_types): code = code.name else: code = to_sector_name(code) return Environment.get_instance().data_proxy.sector(code)
def get_open_orders(): """ 获取当日未成交订单数据 :return: List[:class:`~Order` object] """ return Environment.get_instance().broker.get_open_orders()
def _handle_split(self, trading_date): data_proxy = Environment.get_instance().data_proxy for order_book_id, position in six.iteritems(self._positions): ratio = data_proxy.get_split_by_ex_date(order_book_id, trading_date) if ratio is None: continue position.split_(ratio)
def days_to_expire(self): if self.type != 'Future' or self.order_book_id[ -2:] == '88' or self.order_book_id[-2:] == '99': return -1 date = Environment.get_instance().trading_dt.date() days = (self.maturity_date.date() - date).days return -1 if days < 0 else days
def value_percent(self): """ [float] 获得该持仓的实时市场价值在总投资组合价值中所占比例,取值范围[0, 1] """ accounts = Environment.get_instance().portfolio.accounts if DEFAULT_ACCOUNT_TYPE.STOCK.name not in accounts: return 0 total_value = accounts[DEFAULT_ACCOUNT_TYPE.STOCK.name].total_value return 0 if total_value == 0 else self.market_value / total_value
def universe(self): """ list[`str`] 在运行 :func:`update_universe`, :func:`subscribe` 或者 :func:`unsubscribe` 的时候,合约池会被更新。 需要注意,合约池内合约的交易时间(包含股票的策略默认会在股票交易时段触发)是handle_bar被触发的依据。 """ return Environment.get_instance().get_universe()
def basis_spread(self): try: return self._data['basis_spread'] except (ValueError, KeyError): if self._instrument.type != 'Future' or Environment.get_instance( ).config.base.run_type != RUN_TYPE.PAPER_TRADING: raise if self._basis_spread is None: if self._instrument.underlying_symbol in ['IH', 'IC', 'IF']: order_book_id = self.INDEX_MAP[ self._instrument.underlying_symbol] bar = Environment.get_instance().data_proxy.get_bar( order_book_id, None, '1m') self._basis_spread = self.close - bar.close else: self._basis_spread = np.nan return self._basis_spread
def get_account_type(order_book_id): from bwtougu.environment import Environment instrument = Environment.get_instance().get_instrument(order_book_id) enum_type = instrument.enum_type if enum_type in INST_TYPE_IN_STOCK_ACCOUNT: return DEFAULT_ACCOUNT_TYPE.STOCK.name elif enum_type == INSTRUMENT_TYPE.FUTURE: return DEFAULT_ACCOUNT_TYPE.FUTURE.name else: raise NotImplementedError
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