新增公司资料/指数/重大事项等 13 个接口, 文档全面对齐
- 新增 fin_mda/fin_equity/fin_company/fin_belong_index/fin_major_events 5 个模块 - 覆盖: 管理层讨论、股本结构、公司介绍、所属行业、指数分类/统计/成分、并购/股权激励/诉讼/关联交易/违规/担保 - _emchoice.fetch_report 支持"数据为空"按空结果处理 - api.py 增加 _simple_endpoint 统一生成纯 stocks 参数接口, 路由达 29 个 /fin 端点 - 冒烟测试扩展至 56 用例, 全部通过 - api_manual/blueprint/SKILLS/待优化 文档与代码路径 100% 对齐
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
"""东方财富 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)
|
||||
Reference in New Issue
Block a user