def test_get_trading_period(self): from datetime import time from rqalpha.utils import TimeRange rb_time_range = self.data_proxy.get_trading_period(["RB1912"]) self.assertSetEqual( set(rb_time_range), { TimeRange(start=time(21, 1), end=time(23, 0)), TimeRange(start=time(9, 1), end=time(10, 15)), TimeRange(start=time(10, 31), end=time(11, 30)), TimeRange(start=time(13, 31), end=time(15, 0)) }) merged_time_range = self.data_proxy.get_trading_period( ["AG1912", "TF1912"], [ TimeRange(start=time(9, 31), end=time(11, 30)), TimeRange(start=time(13, 1), end=time(15, 0)), ]) self.assertSetEqual( set(merged_time_range), { TimeRange(start=time(21, 1), end=time(23, 59)), TimeRange(start=time(0, 0), end=time(2, 30)), TimeRange(start=time(9, 1), end=time(11, 30)), TimeRange(start=time(13, 1), end=time(15, 15)), })
def trading_hours(self): # trading_hours='09:31-11:30,13:01-15:00' try: trading_hours = self.__dict__["trading_hours"] except KeyError: if self.type in INST_TYPE_IN_STOCK_ACCOUNT: return self.STOCK_TRADING_PERIOD return None trading_period = [] trading_hours = trading_hours.replace("-", ":") for time_range_str in trading_hours.split(","): start_h, start_m, end_h, end_m = (int(i) for i in time_range_str.split(":")) start, end = datetime.time(start_h, start_m), datetime.time(end_h, end_m) if start > end: trading_period.append(TimeRange(start, datetime.time(23, 59))) trading_period.append(TimeRange(datetime.time(0, 0), end)) else: trading_period.append(TimeRange(start, end)) return trading_period
class Instrument(metaclass=PropertyReprMeta): DEFAULT_LISTED_DATE = datetime.datetime(1990, 1, 1) DEFAULT_DE_LISTED_DATE = datetime.datetime(2999, 12, 31) @staticmethod def _fix_date(ds, dflt): if isinstance(ds, datetime.datetime): return ds if ds == '0000-00-00': return dflt year, month, day = ds.split('-') return datetime.datetime(int(year), int(month), int(day)) __repr__ = property_repr def __init__(self, dic, future_tick_size_getter=None): # type: (Dict, Optional[Callable[[Instrument], float]]) -> None self.__dict__ = copy.copy(dic) self._future_tick_size_getter = future_tick_size_getter if "listed_date" in dic: self.__dict__["listed_date"] = self._fix_date( dic["listed_date"], self.DEFAULT_LISTED_DATE) if "de_listed_date" in dic: self.__dict__["de_listed_date"] = self._fix_date( dic["de_listed_date"], self.DEFAULT_DE_LISTED_DATE) if "maturity_date" in self.__dict__: self.__dict__["maturity_date"] = self._fix_date( dic["maturity_date"], self.DEFAULT_DE_LISTED_DATE) if 'contract_multiplier' in dic: if np.isnan(self.contract_multiplier): raise RuntimeError( "Contract multiplier of {} is not supposed to be nan". format(self.order_book_id)) @property def order_book_id(self): # type: () -> str """ [str] 股票:证券代码,证券的独特的标识符。应以’.XSHG’或’.XSHE’结尾,前者代表上证,后者代表深证。 期货:期货代码,期货的独特的标识符(郑商所期货合约数字部分进行了补齐。例如原有代码’ZC609’补齐之后变为’ZC1609’)。 主力连续合约UnderlyingSymbol+88,例如’IF88’ ;指数连续合约命名规则为UnderlyingSymbol+99 """ return self.__dict__["order_book_id"] @property def symbol(self): # type: () -> str """ [str] 股票:证券的简称,例如’平安银行’。期货:期货的简称,例如’沪深1005’。 """ return self.__dict__["symbol"] @property def round_lot(self): # type: () -> int """ [int] 股票:一手对应多少股,中国A股一手是100股。期货:一律为1。 """ return self.__dict__["round_lot"] @property def listed_date(self): # type: () -> datetime.datetime """ [datetime] 股票:该证券上市日期。期货:期货的上市日期,主力连续合约与指数连续合约都为 datetime(1990, 1, 1)。 """ return self.__dict__["listed_date"] @property def de_listed_date(self): # type: () -> datetime.datetime """ [datetime] 股票:退市日期。期货:交割日期。 """ return self.__dict__["de_listed_date"] @property def type(self): # type: () -> str """ [sty] 合约类型,目前支持的类型有: ‘CS’, ‘INDX’, ‘LOF’, ‘ETF’, ‘Future’ """ return INSTRUMENT_TYPE[self.__dict__["type"]] @property def exchange(self): # type: () -> EXCHANGE """ [str] 交易所。股票:’XSHE’ - 深交所, ‘XSHG’ - 上交所。期货:’DCE’ - 大连商品交易所, ‘SHFE’ - 上海期货交易所, ’CFFEX’ - 中国金融期货交易所, ‘CZCE’- 郑州商品交易所 """ return self.__dict__["exchange"] @property def market_tplus(self): # type: () -> int """ [int] 合约卖出和买入操作需要间隔的最小交易日数,如A股为 1 公募基金的 market_tplus 默认0 """ return self.__dict__.get("market_tplus") or 0 @property def sector_code(self): """ [str] 板块缩写代码,全球通用标准定义(股票专用) """ try: return self.__dict__["sector_code"] except (KeyError, ValueError): raise AttributeError( "Instrument(order_book_id={}) has no attribute 'sector_code' ". format(self.order_book_id)) @property def sector_code_name(self): """ [str] 以当地语言为标准的板块代码名(股票专用) """ try: return self.__dict__["sector_code_name"] except (KeyError, ValueError): raise AttributeError( "Instrument(order_book_id={}) has no attribute 'sector_code_name' " .format(self.order_book_id)) @property def industry_code(self): """ [str] 国民经济行业分类代码,具体可参考“Industry列表” (股票专用) """ try: return self.__dict__["industry_code"] except (KeyError, ValueError): raise AttributeError( "Instrument(order_book_id={}) has no attribute 'industry_code' " .format(self.order_book_id)) @property def industry_name(self): """ [str] 国民经济行业分类名称(股票专用) """ try: return self.__dict__["industry_name"] except (KeyError, ValueError): raise AttributeError( "Instrument(order_book_id={}) has no attribute 'industry_name' " .format(self.order_book_id)) @property def concept_names(self): """ [str] 概念股分类,例如:’铁路基建’,’基金重仓’等(股票专用) """ try: return self.__dict__["concept_names"] except (KeyError, ValueError): raise AttributeError( "Instrument(order_book_id={}) has no attribute 'concept_names' " .format(self.order_book_id)) @property def board_type(self): """ [str] 板块类别,’MainBoard’ - 主板,’GEM’ - 创业板(股票专用) """ try: return self.__dict__["board_type"] except (KeyError, ValueError): raise AttributeError( "Instrument(order_book_id={}) has no attribute 'board_type' ". format(self.order_book_id)) @property def status(self): """ [str] 合约状态。’Active’ - 正常上市, ‘Delisted’ - 终止上市, ‘TemporarySuspended’ - 暂停上市, ‘PreIPO’ - 发行配售期间, ‘FailIPO’ - 发行失败(股票专用) """ try: return self.__dict__["status"] except (KeyError, ValueError): raise AttributeError( "Instrument(order_book_id={}) has no attribute 'status' ". format(self.order_book_id)) @property def special_type(self): """ [str] 特别处理状态。’Normal’ - 正常上市, ‘ST’ - ST处理, ‘StarST’ - *ST代表该股票正在接受退市警告, ‘PT’ - 代表该股票连续3年收入为负,将被暂停交易, ‘Other’ - 其他(股票专用) """ try: return self.__dict__["special_type"] except (KeyError, ValueError): raise AttributeError( "Instrument(order_book_id={}) has no attribute 'special_type' " .format(self.order_book_id)) @property def contract_multiplier(self): """ [float] 合约乘数,例如沪深300股指期货的乘数为300.0(期货专用) """ return self.__dict__.get('contract_multiplier', 1) @property def margin_rate(self): """ [float] 合约最低保证金率(期货专用) """ return self.__dict__.get("margin_rate", 1) @property def underlying_order_book_id(self): """ [str] 合约标的代码,目前除股指期货(IH, IF, IC)之外的期货合约,这一字段全部为’null’(期货专用) """ try: return self.__dict__["underlying_order_book_id"] except (KeyError, ValueError): raise AttributeError( "Instrument(order_book_id={}) has no attribute 'underlying_order_book_id' " .format(self.order_book_id)) @property def underlying_symbol(self): """ [str] 合约标的代码,目前除股指期货(IH, IF, IC)之外的期货合约,这一字段全部为’null’(期货专用) """ try: return self.__dict__["underlying_symbol"] except (KeyError, ValueError): raise AttributeError( "Instrument(order_book_id={}) has no attribute 'underlying_symbol' " .format(self.order_book_id)) @property def maturity_date(self): # type: () -> datetime.datetime """ [datetime] 到期日 """ try: return self.__dict__["maturity_date"] except (KeyError, ValueError): raise AttributeError( "Instrument(order_book_id={}) has no attribute 'maturity_date' " .format(self.order_book_id)) @property def settlement_method(self): """ [str] 交割方式,’CashSettlementRequired’ - 现金交割, ‘PhysicalSettlementRequired’ - 实物交割(期货专用) """ try: return self.__dict__["settlement_method"] except (KeyError, ValueError): raise AttributeError( "Instrument(order_book_id={}) has no attribute 'settlement_method' " .format(self.order_book_id)) @property def listing(self): """ [bool] 该合约当前日期是否在交易 """ trading_dt = Environment.get_instance().trading_dt return self.listing_at(trading_dt) @property def listed(self): """ [bool] 该合约当前交易日是否已上市 """ return self.listed_at(Environment.get_instance().trading_dt) @property def de_listed(self): """ [bool] 该合约当前交易日是否已退市 """ return self.de_listed_at(Environment.get_instance().trading_dt) @property def account_type(self): if self.type in INST_TYPE_IN_STOCK_ACCOUNT: return DEFAULT_ACCOUNT_TYPE.STOCK elif self.type == INSTRUMENT_TYPE.FUTURE: return DEFAULT_ACCOUNT_TYPE.FUTURE else: raise NotImplementedError def listing_at(self, dt): """ 该合约在指定日期是否在交易 :param dt: datetime.datetime :return: bool """ return self.listed_at(dt) and not self.de_listed_at(dt) def listed_at(self, dt): """ 该合约在指定日期是否已上日 :param dt: datetime.datetime :return: bool """ return self.listed_date <= dt def de_listed_at(self, dt): """ 该合约在指定日期是否已退市 :param dt: datetime.datetime :return: bool """ if self.type in (INSTRUMENT_TYPE.FUTURE, INSTRUMENT_TYPE.OPTION): return dt.date() > self.de_listed_date.date() else: return dt >= self.de_listed_date STOCK_TRADING_PERIOD = [ TimeRange(start=datetime.time(9, 31), end=datetime.time(11, 30)), TimeRange(start=datetime.time(13, 1), end=datetime.time(15, 0)), ] @property def trading_hours(self): # trading_hours='09:31-11:30,13:01-15:00' try: trading_hours = self.__dict__["trading_hours"] except KeyError: if self.type in INST_TYPE_IN_STOCK_ACCOUNT: return self.STOCK_TRADING_PERIOD return None trading_period = [] trading_hours = trading_hours.replace("-", ":") for time_range_str in trading_hours.split(","): start_h, start_m, end_h, end_m = ( int(i) for i in time_range_str.split(":")) start, end = datetime.time(start_h, start_m), datetime.time(end_h, end_m) if start > end: trading_period.append(TimeRange(start, datetime.time(23, 59))) trading_period.append(TimeRange(datetime.time(0, 0), end)) else: trading_period.append(TimeRange(start, end)) return trading_period @property def trading_code(self): # type: () -> str try: return self.__dict__["trading_code"] except (KeyError, ValueError): raise AttributeError( "Instrument(order_book_id={}) has no attribute 'trading_code' " .format(self.order_book_id)) @property def trade_at_night(self): return any( r.start <= datetime.time(4, 0) or r.end >= datetime.time(19, 0) for r in (self.trading_hours or [])) def days_from_listed(self): if self.listed_date == self.DEFAULT_LISTED_DATE: return -1 date = Environment.get_instance().trading_dt.date() if self.de_listed_date.date() < date: return -1 ipo_days = (date - self.listed_date.date()).days return ipo_days if ipo_days >= 0 else -1 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 tick_size(self): # type: () -> float if self.type in (INSTRUMENT_TYPE.CS, INSTRUMENT_TYPE.INDX): return 0.01 elif self.type in ("ETF", "LOF"): return 0.001 elif self.type == INSTRUMENT_TYPE.FUTURE: return self._future_tick_size_getter(self) else: raise NotImplementedError def calc_cash_occupation(self, price, quantity, direction): # type: (float, float, POSITION_DIRECTION) -> float if self.type in INST_TYPE_IN_STOCK_ACCOUNT: return price * quantity elif self.type == INSTRUMENT_TYPE.FUTURE: margin_multiplier = Environment.get_instance( ).config.base.margin_multiplier return price * quantity * self.contract_multiplier * self.margin_rate * margin_multiplier else: raise NotImplementedError