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.
 
 
 
EMWebApi/utils/fin_major_events.py

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)