# -*- coding: utf-8 -*-
# 版权所有 2019 深圳米筐科技有限公司(下称“米筐科技”)
#
# 除非遵守当前许可,否则不得使用本软件。
#
# * 非商业用途(非商业用途指个人出于非商业目的使用本软件,或者高校、研究所等非营利机构出于教育、科研等目的使用本软件):
# 遵守 Apache License 2.0(下称“Apache 2.0 许可”),
# 您可以在以下位置获得 Apache 2.0 许可的副本:http://www.apache.org/licenses/LICENSE-2.0。
# 除非法律有要求或以书面形式达成协议,否则本软件分发时需保持当前许可“原样”不变,且不得附加任何条件。
#
# * 商业用途(商业用途指个人出于任何商业目的使用本软件,或者法人或其他组织出于任何目的使用本软件):
# 未经米筐科技授权,任何个人不得出于任何商业目的使用本软件(包括但不限于向第三方提供、销售、出租、出借、转让本软件、
# 本软件的衍生产品、引用或借鉴了本软件功能或源代码的产品或服务),任何法人或其他组织不得出于任何目的使用本软件,
# 否则米筐科技有权追究相应的知识产权侵权责任。
# 在此前提下,对本软件的使用同样需要遵守 Apache 2.0 许可,Apache 2.0 许可与本许可冲突之处,以本许可为准。
# 详细的授权流程,请联系 public@ricequant.com 获取。
import re
import copy
import datetime
from typing import Dict, Callable, Optional
import numpy as np
from dateutil.parser import parse
from rqalpha.environment import Environment
from rqalpha.const import INSTRUMENT_TYPE, POSITION_DIRECTION, DEFAULT_ACCOUNT_TYPE, EXCHANGE
from rqalpha.utils import TimeRange, INST_TYPE_IN_STOCK_ACCOUNT
from rqalpha.utils.repr import property_repr, PropertyReprMeta
[文档]class Instrument(metaclass=PropertyReprMeta):
DEFAULT_DE_LISTED_DATE = datetime.datetime(2999, 12, 31)
@staticmethod
def _fix_date(ds, dflt=None) -> datetime:
if isinstance(ds, datetime.datetime):
return ds
if ds == '0000-00-00' or ds is None:
return dflt
try:
year, month, day = ds.split('-')
return datetime.datetime(int(year), int(month), int(day))
except:
return parse(ds)
__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"])
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) -> int:
"""
[int] 股票:一手对应多少股,中国A股一手是100股。期货:一律为1。
"""
if self.__dict__.get("type") == INSTRUMENT_TYPE.CS and self.__dict__["board_type"] == "KSH":
return 1
return int(self.__dict__["round_lot"])
@property
def listed_date(self) -> Optional[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 and 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 during_call_auction(self, dt):
""" 是否处于集合竞价交易时段 """
# 当前的分钟数
_minute = dt.hour * 60 + dt.minute
if self.type in [INSTRUMENT_TYPE.CS, INSTRUMENT_TYPE.ETF]:
# 9:30 前或 14:57 之后为集合竞价
return _minute < 9 * 60 + 30 or _minute >= 14 * 60 + 57
elif self.type == INSTRUMENT_TYPE.FUTURE:
# 期货开盘时间
start_time = self.trading_hours[0].start
# -1 是因为获取到的时间都是开盘后1分钟,如 09:31
start_minute = start_time.hour * 60 + start_time.minute - 1
# 开盘集合竞价时间段为开盘前5分钟,期货无收盘集合竞价
return start_minute - 5 <= _minute < start_minute
else:
# 其他品种由子类实现
return False
def days_from_listed(self):
if not self.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.is_future_continuous_contract(self.order_book_id):
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
FUTURE_CONTINUOUS_CONTRACT = re.compile(r"^[A-Z]+(88|888|99|889)([A-Z]\d+)?$")
@classmethod
def is_future_continuous_contract(cls, order_book_id):
return re.match(cls.FUTURE_CONTINUOUS_CONTRACT, order_book_id)
class SectorCodeItem(object):
def __init__(self, cn, en, name):
self.__cn = cn
self.__en = en
self.__name = name
@property
def cn(self):
return self.__cn
@property
def en(self):
return self.__en
@property
def name(self):
return self.__name
def __repr__(self):
return "{}: {}, {}".format(self.__name, self.__en, self.__cn)
class SectorCode(object):
Energy = SectorCodeItem("能源", "energy", 'Energy')
Materials = SectorCodeItem("原材料", "materials", 'Materials')
ConsumerDiscretionary = SectorCodeItem("非必需消费品", "consumer discretionary", 'ConsumerDiscretionary')
ConsumerStaples = SectorCodeItem("必需消费品", "consumer staples", 'ConsumerStaples')
HealthCare = SectorCodeItem("医疗保健", "health care", 'HealthCare')
Financials = SectorCodeItem("金融", "financials", 'Financials')
InformationTechnology = SectorCodeItem("信息技术", "information technology", 'InformationTechnology')
TelecommunicationServices = SectorCodeItem("电信服务", "telecommunication services", 'TelecommunicationServices')
Utilities = SectorCodeItem("公共服务", "utilities", 'Utilities')
Industrials = SectorCodeItem("工业", "industrials", "Industrials")
class IndustryCodeItem(object):
def __init__(self, code, name):
self.__code = code
self.__name = name
@property
def code(self):
return self.__code
@property
def name(self):
return self.__name
def __repr__(self):
return "{0}:{1}".format(self.__code, self.__name)
class IndustryCode(object):
A01 = IndustryCodeItem("A01", "农业")
A02 = IndustryCodeItem("A02", "林业")
A03 = IndustryCodeItem("A03", "畜牧业")
A04 = IndustryCodeItem("A04", "渔业")
A05 = IndustryCodeItem("A05", "农、林、牧、渔服务业")
B06 = IndustryCodeItem("B06", "煤炭开采和洗选业")
B07 = IndustryCodeItem("B07", "石油和天然气开采业")
B08 = IndustryCodeItem("B08", "黑色金属矿采选业")
B09 = IndustryCodeItem("B09", "有色金属矿采选业")
B10 = IndustryCodeItem("B10", "非金属矿采选业")
B11 = IndustryCodeItem("B11", "开采辅助活动")
B12 = IndustryCodeItem("B12", "其他采矿业")
C13 = IndustryCodeItem("C13", "农副食品加工业")
C14 = IndustryCodeItem("C14", "食品制造业")
C15 = IndustryCodeItem("C15", "酒、饮料和精制茶制造业")
C16 = IndustryCodeItem("C16", "烟草制品业")
C17 = IndustryCodeItem("C17", "纺织业")
C18 = IndustryCodeItem("C18", "纺织服装、服饰业")
C19 = IndustryCodeItem("C19", "皮革、毛皮、羽毛及其制品和制鞋业")
C20 = IndustryCodeItem("C20", "木材加工及木、竹、藤、棕、草制品业")
C21 = IndustryCodeItem("C21", "家具制造业")
C22 = IndustryCodeItem("C22", "造纸及纸制品业")
C23 = IndustryCodeItem("C23", "印刷和记录媒介复制业")
C24 = IndustryCodeItem("C24", "文教、工美、体育和娱乐用品制造业")
C25 = IndustryCodeItem("C25", "石油加工、炼焦及核燃料加工业")
C26 = IndustryCodeItem("C26", "化学原料及化学制品制造业")
C27 = IndustryCodeItem("C27", "医药制造业")
C28 = IndustryCodeItem("C28", "化学纤维制造业")
C29 = IndustryCodeItem("C29", "橡胶和塑料制品业")
C30 = IndustryCodeItem("C30", "非金属矿物制品业")
C31 = IndustryCodeItem("C31", "黑色金属冶炼及压延加工业")
C32 = IndustryCodeItem("C32", "有色金属冶炼和压延加工业")
C33 = IndustryCodeItem("C33", "金属制品业")
C34 = IndustryCodeItem("C34", "通用设备制造业")
C35 = IndustryCodeItem("C35", "专用设备制造业")
C36 = IndustryCodeItem("C36", "汽车制造业")
C37 = IndustryCodeItem("C37", "铁路、船舶、航空航天和其它运输设备制造业")
C38 = IndustryCodeItem("C38", "电气机械及器材制造业")
C39 = IndustryCodeItem("C39", "计算机、通信和其他电子设备制造业")
C40 = IndustryCodeItem("C40", "仪器仪表制造业")
C41 = IndustryCodeItem("C41", "其他制造业")
C42 = IndustryCodeItem("C42", "废弃资源综合利用业")
C43 = IndustryCodeItem("C43", "金属制品、机械和设备修理业")
D44 = IndustryCodeItem("D44", "电力、热力生产和供应业")
D45 = IndustryCodeItem("D45", "燃气生产和供应业")
D46 = IndustryCodeItem("D46", "水的生产和供应业")
E47 = IndustryCodeItem("E47", "房屋建筑业")
E48 = IndustryCodeItem("E48", "土木工程建筑业")
E49 = IndustryCodeItem("E49", "建筑安装业")
E50 = IndustryCodeItem("E50", "建筑装饰和其他建筑业")
F51 = IndustryCodeItem("F51", "批发业")
F52 = IndustryCodeItem("F52", "零售业")
G53 = IndustryCodeItem("G53", "铁路运输业")
G54 = IndustryCodeItem("G54", "道路运输业")
G55 = IndustryCodeItem("G55", "水上运输业")
G56 = IndustryCodeItem("G56", "航空运输业")
G57 = IndustryCodeItem("G57", "管道运输业")
G58 = IndustryCodeItem("G58", "装卸搬运和运输代理业")
G59 = IndustryCodeItem("G59", "仓储业")
G60 = IndustryCodeItem("G60", "邮政业")
H61 = IndustryCodeItem("H61", "住宿业")
H62 = IndustryCodeItem("H62", "餐饮业")
I63 = IndustryCodeItem("I63", "电信、广播电视和卫星传输服务")
I64 = IndustryCodeItem("I64", "互联网和相关服务")
I65 = IndustryCodeItem("I65", "软件和信息技术服务业")
J66 = IndustryCodeItem("J66", "货币金融服务")
J67 = IndustryCodeItem("J67", "资本市场服务")
J68 = IndustryCodeItem("J68", "保险业")
J69 = IndustryCodeItem("J69", "其他金融业")
K70 = IndustryCodeItem("K70", "房地产业")
L71 = IndustryCodeItem("L71", "租赁业")
L72 = IndustryCodeItem("L72", "商务服务业")
M73 = IndustryCodeItem("M73", "研究和试验发展")
M74 = IndustryCodeItem("M74", "专业技术服务业")
M75 = IndustryCodeItem("M75", "科技推广和应用服务业")
N76 = IndustryCodeItem("N76", "水利管理业")
N77 = IndustryCodeItem("N77", "生态保护和环境治理业")
N78 = IndustryCodeItem("N78", "公共设施管理业")
O79 = IndustryCodeItem("O79", "居民服务业")
O80 = IndustryCodeItem("O80", "机动车、电子产品和日用产品修理业")
O81 = IndustryCodeItem("O81", "其他服务业")
P82 = IndustryCodeItem("P82", "教育")
Q83 = IndustryCodeItem("Q83", "卫生")
Q84 = IndustryCodeItem("Q84", "社会工作")
R85 = IndustryCodeItem("R85", "新闻和出版业")
R86 = IndustryCodeItem("R86", "广播、电视、电影和影视录音制作业")
R87 = IndustryCodeItem("R87", "文化艺术业")
R88 = IndustryCodeItem("R88", "体育")
R89 = IndustryCodeItem("R89", "娱乐业")
S90 = IndustryCodeItem("S90", "综合")