You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
222 lines
8.0 KiB
222 lines
8.0 KiB
"""东方财富 CHOICE API 重大事项抓取工具 (并购/股权激励/诉讼/关联交易/违规/担保)。
|
|
- 输入: 股票代码列表 (如 ['603233.SH'])
|
|
- 输出: pandas DataFrame, 六类事项各一个入口函数
|
|
- 不需要登录, 不需要 cookie, 无文件落盘
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import pandas as pd
|
|
|
|
from utils._emchoice import fetch_report
|
|
|
|
|
|
def _date_range(years: int) -> tuple[str, str]:
|
|
"""按年份数生成 [start, end] 日期区间."""
|
|
today = pd.Timestamp.today()
|
|
start = (today - pd.DateOffset(years=years)).strftime("%Y-%m-%d")
|
|
end = today.strftime("%Y-%m-%d")
|
|
return start, end
|
|
|
|
|
|
def _year_range(years: int) -> tuple[str, str]:
|
|
"""按年份数生成 [start_year, end_year] 区间 (字符串)."""
|
|
end_year = pd.Timestamp.today().year
|
|
start_year = end_year - years + 1
|
|
return str(start_year), str(end_year)
|
|
|
|
|
|
def _fetch_one(secucode: str, years: int, build_params) -> pd.DataFrame:
|
|
"""按给定参数构造器拉一只股票的数据."""
|
|
rows = fetch_report(build_params(secucode, years))
|
|
df = pd.DataFrame(rows)
|
|
if not df.empty:
|
|
df["SECUCODE"] = secucode
|
|
return df
|
|
|
|
|
|
def _run(secucodes: list[str], years: int, build_params) -> pd.DataFrame:
|
|
codes = [code.strip() for code in secucodes if code.strip()]
|
|
if not codes:
|
|
return pd.DataFrame()
|
|
|
|
frames = [_fetch_one(code, years, build_params) for code in codes]
|
|
frames = [df for df in frames if not df.empty]
|
|
if not frames:
|
|
return pd.DataFrame()
|
|
return pd.concat(frames, ignore_index=True)
|
|
|
|
|
|
# ---------------------------------------------------------------- 并购事件
|
|
|
|
def _build_acquisitions_params(secucode: str, years: int) -> dict[str, str]:
|
|
start, end = _date_range(years)
|
|
return {
|
|
"reportName": "RPT_HS_ACQUISITIONS_EVENT",
|
|
"columns": (
|
|
"SECUCODE,SECURITY_CODE,FIRST_NOTICE_DATE,NOTICE_DATE,PLAN_PROCESS,"
|
|
"NAME_OBJ,NAME_BUY,MXID,NAME_SELL,DEAL_AMT,TRANSFER_RATIO,TRADTE_CURRENCY"
|
|
),
|
|
"quoteColumns": "",
|
|
"filter": (
|
|
f'(SECUCODE="{secucode}")(FIRST_NOTICE_DATE>=\'{start}\')'
|
|
f"(FIRST_NOTICE_DATE<='{end}')"
|
|
),
|
|
"sortTypes": "-1,1",
|
|
"sortColumns": "FIRST_NOTICE_DATE,MXID",
|
|
"requestParams": "1",
|
|
"pageNumber": "1",
|
|
"pageSize": "50",
|
|
"client": "SW",
|
|
}
|
|
|
|
|
|
def get_acquisitions(secucodes: list[str], years: int = 3) -> pd.DataFrame:
|
|
"""抓取指定股票的并购事件。"""
|
|
return _run(secucodes, years, _build_acquisitions_params)
|
|
|
|
|
|
# ---------------------------------------------------------------- 股权激励
|
|
|
|
def _build_equity_incentive_params(secucode: str, years: int) -> dict[str, str]:
|
|
start, end = _date_range(years)
|
|
return {
|
|
"reportName": "RPT_DMSK_SH_EQUITYINCENTIVE",
|
|
"columns": (
|
|
"SECUCODE,ORG_CODE,PLAN_PROCESS,EI_TARGET,EI_WAY,TOTAL_INCENTIVE_SHARES,"
|
|
"TOTAL_INCENTIVE_TSR,INITIAL_EXERCISE_PRICE,VALID_EXPIRE,PLAN_NOTICE_DATE,"
|
|
"GMDECISION_NOTICE_DATE,FIRST_NOTICE_DATE,PLAN_EXPLAIN,PRICE_EXPLAIN,"
|
|
"INCENTIVE_SELL_EXPLAIN,INCENTIVE_GRANT_CONDITION,EXERCISE_SPECIAL_CONDITION,"
|
|
"NOTICE_DATE,STR_YEAR,SECURITY_NAME_ABBR,SECURITY_CODE,INDUSTRYCSRC,"
|
|
"EM_INDUSTRY,PLAN_NUM"
|
|
),
|
|
"filter": (
|
|
f'(SECUCODE="{secucode}")(PLAN_NOTICE_DATE>=\'{start}\')'
|
|
f"(PLAN_NOTICE_DATE<='{end}')"
|
|
),
|
|
"sortTypes": "-1",
|
|
"sortColumns": "PLAN_NOTICE_DATE",
|
|
"source": "CHOICE",
|
|
"client": "SW",
|
|
}
|
|
|
|
|
|
def get_equity_incentive(secucodes: list[str], years: int = 3) -> pd.DataFrame:
|
|
"""抓取指定股票的股权激励方案。"""
|
|
return _run(secucodes, years, _build_equity_incentive_params)
|
|
|
|
|
|
# ---------------------------------------------------------------- 诉讼仲裁
|
|
|
|
def _build_litigation_params(secucode: str, years: int) -> dict[str, str]:
|
|
start, end = _year_range(years)
|
|
return {
|
|
"source": "CHOICE",
|
|
"reportName": "RPT_LITIGATION_ARBITRATION_BSINFO",
|
|
"columns": (
|
|
"ORG_CODE,SECUCODE,SECURITY_CODE,SECURITY_NAME_ABBR,NOTICE_DATE,"
|
|
"CASE_NAME,CASE_PROFILE,PLAINTIFF,DEFENCE,LITIGATION_TYPE,"
|
|
"FI_PROSECUTE_DATE,FI_ACCEPTANCE_COURT,CASE_AMOUNT,FI_JUDGMENT_DATE,"
|
|
"FI_JUDGMENT_CONTENT,FI_IS_APPEAL,SI_APPELLEE,SI_ACCEPTANCE_COURT,"
|
|
"SI_JUDGMENT_DATE,SI_JUDGMENT_CONTENT,EXECUTE_SITUATION,CURRENCY,NOTICE_YEAR"
|
|
),
|
|
"quoteColumns": "",
|
|
"filter": f'(SECUCODE="{secucode}")(NOTICE_YEAR>="{start}")(NOTICE_YEAR<="{end}")',
|
|
"pageNumber": "",
|
|
"pageSize": "",
|
|
"sortTypes": "-1",
|
|
"sortColumns": "NOTICE_DATE",
|
|
"client": "SW",
|
|
}
|
|
|
|
|
|
def get_litigation(secucodes: list[str], years: int = 3) -> pd.DataFrame:
|
|
"""抓取指定股票的诉讼仲裁信息。"""
|
|
return _run(secucodes, years, _build_litigation_params)
|
|
|
|
|
|
# ---------------------------------------------------------------- 关联交易
|
|
|
|
def _build_related_transaction_params(secucode: str, years: int) -> dict[str, str]:
|
|
start, end = _date_range(years)
|
|
return {
|
|
"reportName": "RPT_RELATED_TRANSACTION",
|
|
"columns": (
|
|
"SECUCODE,SECURITY_CODE,SECURITY_NAME_ABBR,ORG_CODE,NOTICE_DATE,"
|
|
"RELATED_PARTY,RELATED_RELATION,IS_CONTROL,TRADE_AMT,PAY_WAY,"
|
|
"TRADE_PROFILE,PRICE_BASIS,TRADE_START_DATE,TRADE_END_DATE,CURRENCY,STR_YEAR"
|
|
),
|
|
"quoteColumns": "",
|
|
# 抓包中该报表 filter 含分号, 保留原样
|
|
"filter": (
|
|
f'(SECUCODE="{secucode}");(NOTICE_DATE>=\'{start}\')'
|
|
f"(NOTICE_DATE<='{end}')"
|
|
),
|
|
"pageNumber": "",
|
|
"pageSize": "",
|
|
"sortTypes": "-1",
|
|
"sortColumns": "NOTICE_DATE",
|
|
"client": "SW",
|
|
"source": "CHOICE",
|
|
}
|
|
|
|
|
|
def get_related_transaction(secucodes: list[str], years: int = 3) -> pd.DataFrame:
|
|
"""抓取指定股票的关联交易。"""
|
|
return _run(secucodes, years, _build_related_transaction_params)
|
|
|
|
|
|
# ---------------------------------------------------------------- 违规
|
|
|
|
def _build_violation_params(secucode: str, years: int) -> dict[str, str]:
|
|
start, end = _date_range(years)
|
|
return {
|
|
"source": "CHOICE",
|
|
"reportName": "RPT_HSF9_OP_VIOLATION",
|
|
"columns": (
|
|
"ORG_CODE,NOTICE_DATE,NOTICE_YEAR,CURRENCY,SECUCODE,SECURITY_CODE,"
|
|
"SECURITY_NAME_ABBR,SOLVE_ORG,VIOLATE_TYPE,PUNISH_TYPE,PUNISH_AMT,"
|
|
"VIOLATE_BEHAVIOUR,PUNISH_STEP,PUNISH_OBJECT"
|
|
),
|
|
"quoteColumns": "",
|
|
# 抓包中该报表 secucode 为小写, 保留原样
|
|
"filter": (
|
|
f'(secucode="{secucode}")(NOTICE_DATE>=\'{start}\')'
|
|
f"(NOTICE_DATE<='{end}')"
|
|
),
|
|
"pageNumber": "1",
|
|
"pageSize": "",
|
|
"sortTypes": "-1",
|
|
"sortColumns": "NOTICE_DATE",
|
|
"client": "SW",
|
|
}
|
|
|
|
|
|
def get_violation(secucodes: list[str], years: int = 3) -> pd.DataFrame:
|
|
"""抓取指定股票的违规处罚记录。"""
|
|
return _run(secucodes, years, _build_violation_params)
|
|
|
|
|
|
# ---------------------------------------------------------------- 担保
|
|
|
|
def _build_guarantee_params(secucode: str, years: int) -> dict[str, str]:
|
|
start, end = _year_range(years)
|
|
return {
|
|
"source": "CHOICE",
|
|
"reportName": "RPT_F9_GUARANTEE",
|
|
"columns": (
|
|
"SECURITY_CODE,SECUCODE,SECURITY_NAME_ABBR,REPORT_DATE,GUAR_NAME,"
|
|
"GUARANTEED_NAME,GUARANTEE_START_DATE,GUARANTEE_END_DATE,IS_PERFORM,"
|
|
"TRADE_DATE,GUARANTEE_WAY,GUARANTEE_AMT,GUARANTEE_EXPIRE,"
|
|
"IS_RELATED_TRADE,CURRENCY,REPORT_YEAR,HISTORYCHG,ORG_CODE"
|
|
),
|
|
"quoteColumns": "",
|
|
"filter": f'(SECUCODE="{secucode}")(REPORT_YEAR>="{start}")(REPORT_YEAR<="{end}")',
|
|
"sortTypes": "-1",
|
|
"sortColumns": "REPORT_DATE",
|
|
"client": "SW",
|
|
}
|
|
|
|
|
|
def get_guarantee(secucodes: list[str], years: int = 3) -> pd.DataFrame:
|
|
"""抓取指定股票的对外担保记录。"""
|
|
return _run(secucodes, years, _build_guarantee_params)
|
|
|