1. 项目概述为什么我们需要一份干净的A股风险警示标签数据如果你在A股市场做量化研究、因子挖掘或者公司财务分析有一个问题几乎无法回避如何处理那些被贴上“ST”、“*ST”甚至更早的“PT”标签的股票这些标签是交易所对存在特定风险比如连续亏损、财务造假、经营异常的上市公司施加的特殊处理标识。简单来说它们就像是股票代码上的“黄牌”或“红牌”。在构建投资组合、计算市场指数或者进行学术研究时如果不把这些“问题股”剔除你的回测结果可能会被严重扭曲。想象一下你设计了一个基于盈利能力的选股策略结果里面混入了一堆因为连续亏损而被ST的公司策略的有效性从源头上就打了折扣。然而获取一份准确、完整且易于使用的A股风险警示历史数据并不是一件简单的事。交易所的公告是分散的数据格式不一手动整理费时费力且容易出错。更麻烦的是ST状态是动态变化的一家公司今年被ST明年可能因为情况好转而“摘帽”也可能情况恶化变成*ST甚至最终退市。这就需要一份按时间序列记录每家上市公司风险警示状态的数据面板。这正是本项目要解决的问题整理一份从A股市场诞生至今所有上市公司是否被ST、*ST或PT的标识数据并附上清晰、可复现的Stata整理代码数据更新至2023年。有了这套“工具包”研究者可以一键筛选出任意时间点上的“正常股”确保分析基础的洁净。2. 核心需求解析与数据来源设计2.1 明确“ST/*ST/PT”的定义与影响首先我们必须厘清这几个标签的具体含义和演变这是数据准确性的基石。PT (Particular Transfer特别转让)这是历史产物主要在2000年代初用于暂停上市的公司。这类股票只能在每周五进行集合竞价转让流动性极差。随着退市制度的完善PT制度已基本退出舞台但在整理早期历史数据时必须包含。ST (Special Treatment特别处理)这是当前最常见的风险警示。公司被ST通常因为财务状况异常例如最近两个会计年度净利润为负或最近一年净资产为负。被ST后股票日涨跌幅限制从10%缩小为5%意在提示风险。*ST (退市风险警示)在ST基础上风险进一步升级。通常因为情况比ST更严重例如连续三年亏损或存在重大违法强制退市情形。*ST是退市前的最后一道警示涨跌幅同样为5%。对于研究而言这些股票通常需要被排除原因有三1)财务异常其财务数据本身已失真或处于非正常状态不具可比性2)交易规则异化涨跌幅限制不同影响收益率计算的公平性3)样本污染它们往往伴随极端的价格波动连续跌停或涨停会严重干扰因子有效性检验和市场收益率计算。2.2 数据来源的选取与挑战整理这类数据可靠的数据源是关键。通常有三个主要渠道交易所官方公告最权威的来源。上海证券交易所和深圳证券交易所会发布关于对上市公司实施ST、*ST及撤销处理的公告。但问题在于公告是文本格式且历史公告的获取和解析爬虫工作量巨大需要处理PDF、HTML等多种格式并准确提取公司代码、全称、实施日期等关键信息。专业金融数据终端如Wind、Choice、iFinD等。它们有结构化的风险警示历史数据但属于付费商业数据且直接导出可能无法满足个性化的时间频率如日度或字段需求。开源数据社区与学术数据库如CNRDS、CSMAR等学术数据库或GitHub上的一些开源项目。这些数据质量参差不齐需要仔细核对但通常是研究者尤其是学生和独立研究者性价比最高的起点。本项目的设计思路是以开源/学术数据为基础通过交叉验证和逻辑规则清洗构建高质量面板数据。具体而言我们可以从CSMAR或CNRDS获取上市公司基本资料表中的“是否ST/PT”字段作为基础但这通常是季度或年度数据且可能存在缺失或错误。然后我们需要结合交易所公告日期可从网络爬取或利用现有整理好的公告日期列表进行精细化处理将状态变更精确到“日”级别并编写逻辑规则如“摘帽”后状态应归为正常来修正数据矛盾。注意数据源的任何微小错误都可能导致最终结果出现系统性偏差。例如如果漏掉了一家公司在某段时间的ST状态那么在回测中就会错误地将其纳入样本。因此在代码中必须设置多重校验和人工抽查环节。3. 数据整理的核心步骤与Stata实现3.1 数据准备与初步清洗假设我们已经从CSMAR数据库下载了“中国上市公司基本信息表”表名通常如Company_Background其中包含字段Stkcd股票代码、Listdt上市日期、Enddt退市日期、State上市状态以及我们最关心的RiskWarning风险警示类型可能用代码表示如‘ST’ ‘*ST’ ‘S’等不同数据库编码方式不同。第一步是在Stata中导入并理解数据结构。* 假设数据已保存为company_info.dta use path/to/company_info.dta, clear * 查看变量标签和内容 describe codebook RiskWarning, compact tab RiskWarning这一步的目的是确认RiskWarning字段的编码规则。例如可能发现“1”代表正常“2”代表ST“3”代表*ST“4”代表PT或者直接用字符串‘ST’、‘*ST’表示。我们需要将其统一转换为便于理解的分类变量。* 示例根据编码创建新的分类变量 gen risk_type replace risk_type 正常 if RiskWarning 1 replace risk_type ST if RiskWarning 2 | RiskWarning ST replace risk_type *ST if RiskWarning 3 | RiskWarning *ST replace risk_type PT if RiskWarning 4 | RiskWarning PT replace risk_type 其他 if missing(risk_type) !missing(RiskWarning) label var risk_type 风险警示类型 * 检查转换结果 tab risk_type但这里有一个核心问题数据库中的这个字段通常是报告期如年报、季报的截面状态。我们需要的是日度面板数据即对于每一天每只股票都有一个风险警示状态。这就需要进行时间序列的扩展和插值。3.2 构建日度风险警示状态面板构建日度面板的核心逻辑是“状态持续假设”即一家公司的风险警示状态从生效日开始持续到下一次状态变更日或退市日的前一天。首先我们需要一份A股全市场所有股票、所有交易日的日期面板作为骨架。* 生成一个包含所有交易日期的文件trading_dates.dta可从行情数据中提取唯一日期获得 use path/to/all_stock_daily_prices.dta, clear keep date duplicates drop date, force sort date save trading_dates.dta, replace然后我们需要一份记录了每家股票风险警示状态变更事件的数据。理想情况下这份数据应包含Stkcd股票代码、change_date状态变更生效日、new_risk_type变更后的新状态。这份数据可以通过解析交易所公告获得是整理工作的核心难点和价值所在。假设我们已经通过爬虫和人工核对得到了一份相对干净的变更事件表risk_change_events.dta。* 步骤1为每只股票创建其生命周期的日期序列 use company_info.dta, clear keep Stkcd Listdt Enddt expand (Enddt - Listdt 1) // 假设Enddt存在若未退市则为最新日期 bysort Stkcd: gen date Listdt _n - 1 format date %td keep if dow(date)!0 dow(date)!6 // 粗略剔除周末更精确应用交易日历 save stock_daily_skeleton.dta, replace * 步骤2与状态变更事件表合并 merge m:1 Stkcd date using risk_change_events.dta, keepusing(new_risk_type) rename new_risk_type risk_type_event接下来是关键的一步使用Stata的carryforward命令或fillin 排序逻辑来填充状态。我们假设在第一次状态变更前股票状态为“正常”。* 步骤3填充状态 bysort Stkcd (date): gen risk_type_daily risk_type_event * 对于每个股票将非缺失值向前填充locf, last observation carried forward bysort Stkcd (date): replace risk_type_daily risk_type_daily[_n-1] if missing(risk_type_daily) _n1 * 处理初始状态上市首日到第一次事件日之间 bysort Stkcd (date): replace risk_type_daily 正常 if missing(risk_type_daily) _n1然而现实情况往往更复杂。变更事件表可能有缺失或者数据库中的季度状态数据与事件日期不完全匹配。这时就需要引入第二数据源进行交叉验证与冲突解决。3.3 多源数据交叉验证与冲突解决我们可以引入另一个数据源比如从Wind导出的月度ST状态列表。将其处理成与我们的日度面板相同格式Stkcd,date,risk_type_wind然后进行合并比对。merge 1:1 Stkcd date using wind_risk_monthly.dta, keepusing(risk_type_wind)合并后risk_type_daily来自事件填充和risk_type_wind来自Wind月度之间可能出现不一致。我们需要制定一套冲突解决规则两者一致直接采纳。*Wind显示为ST/ST而事件填充为正常这可能是我们的事件表漏记了。需要以Wind数据为线索回溯查找该时间段内的交易所公告进行核实。在代码中我们可以先将其标记为“待核实”并输出到日志文件。*事件填充为ST/ST而Wind显示为正常同样需要核实。有时Wind的数据更新存在滞后事件数据可能更及时。两者均为缺失通常意味着该日股票状态为正常。在Stata中我们可以这样实现一个简单的冲突标记逻辑gen conflict_flag 0 gen risk_type_final risk_type_daily * 规则当Wind数据非缺失且与当前填充数据不同时标记冲突并优先采用更严格的警示状态即*ST ST 正常 replace conflict_flag 1 if !missing(risk_type_wind) risk_type_wind ! risk_type_daily * 定义一个状态严重程度的数值 gen severity 0 replace severity 1 if risk_type_daily ST replace severity 2 if risk_type_daily *ST replace severity 3 if risk_type_daily PT gen severity_wind 0 replace severity_wind 1 if risk_type_wind ST replace severity_wind 2 if risk_type_wind *ST replace severity_wind 3 if risk_type_wind PT * 冲突时取严重程度更高的状态 replace risk_type_final risk_type_wind if conflict_flag 1 severity_wind severity处理完冲突后我们得到的就是一份初步的、经过校验的日度风险警示状态面板risk_panel_preliminary.dta。4. 数据质量检查与常见问题处理4.1 逻辑一致性检查数据整理出来后必须进行严格的逻辑检查以下是一些必做的检查项及其Stata实现状态连续性检查同一只股票其风险警示状态的变化应符合“正常-ST-ST-退市”或“正常-PT”的基本路径不应出现跳跃如直接从正常变为ST虽然罕见但需核查或反复横跳。bysort Stkcd (date): gen prev_type risk_type_final[_n-1] gen illogical_change 0 * 例如检查是否出现“正常”直接变“*ST”跳过ST通常需要结合具体规则这里仅示例 replace illogical_change 1 if risk_type_final*ST prev_type正常 list Stkcd date prev_type risk_type_final if illogical_change 1与退市状态的兼容性检查已经退市的股票在退市日期之后不应再有任何状态记录。同时在退市前其状态很可能就是*ST。merge m:1 Stkcd using “company_info.dta” keepusing(Enddt) gen after_delist date Enddt !missing(Enddt) count if after_delist 1 !missing(risk_type_final) * 如果计数0说明退市后还有数据需要清理关键日期的验证随机抽取若干次知名的ST/*ST事件例如某些财务造假大案的公司核对我们的数据中状态变更的日期是否与公开报道的生效日一致。4.2 缺失值与边界情况处理上市初期新股上市初期不可能被ST。我们的数据在上市日期到首次财报披露日之间应强制设为“正常”。长时间停牌期间股票停牌时其风险警示状态是冻结的。我们的日度面板在停牌日依然保留其状态是没问题的因为我们需要的是日历日的状态标识。但需注意在计算收益率等需要交易数据的指标时这些日期自然会被剔除。B股、科创板、创业板等特殊板块ST规则基本一致但科创板、创业板试点注册制后退市流程有所变化。数据源需要覆盖全市场代码处理逻辑上通常无需特别区分但需知晓规则背景。4.3 生成最终易用的指标对于大多数研究而言我们最终需要的往往不是一个字符串分类变量而是一个或多个简洁的二进制0/1指标便于在回归或筛选时直接使用。* 生成最常用的“是否被风险警示”指标 gen is_risk_warning (inlist(risk_type_final, ST, *ST, PT)) label var is_risk_warning 是否被ST/*ST/PT (1是) * 有时需要区分ST和*ST gen is_ST (risk_type_final ST) gen is_STstar (risk_type_final *ST) label var is_ST 是否为ST (1是) label var is_STstar 是否为*ST (1是) * 保存最终数据集 keep Stkcd date is_risk_warning is_ST is_STstar risk_type_final order Stkcd date sort Stkcd date save A_share_ST_data_2023.dta, replace最终的数据集应包含股票代码、日期、以及几个核心的指示变量。文件不宜过大可以通过compress命令优化存储。5. Stata高级技巧效率优化与批量处理当处理全市场、长达三十多年的日度数据时数据量可能达到千万行级别。原始的循环操作会极其缓慢。必须运用Stata的向量化操作和高效命令。避免循环如上文所示使用bysort配合_n,_N以及replace ... if ...的向量化操作效率远高于forvalues循环。使用joinby替代多层嵌套循环在创建股票-日期骨架时如果股票数量多expand日期差的方法可能内存消耗大。另一种更高效的方法是使用joinby。* 方法二使用joinby (更高效处理大量股票) use “company_info.dta” clear keep Stkcd Listdt Enddt cross using “trading_dates.dta” keep if date Listdt (date Enddt | missing(Enddt))合理使用preserve/restore和临时文件在复杂的多步骤清洗中将中间结果保存为临时文件tempfile可以释放内存并使流程更清晰。tempfile skeleton events merged preliminary final save skeleton ... merge 1:1 Stkcd date using events save merged利用collapse快速汇总统计如果你想快速了解每年被ST的公司数量collapse命令是神器。use “A_share_ST_data_2023.dta” clear gen year year(date) collapse (sum) count_ST is_ST count_STstar is_STstar, by(year) line count_ST count_STstar year, title(“A股历年ST与*ST公司数量”)这正是热词中“stata的collapse怎么画图”的一个典型应用场景先collapse汇总再使用twoway line等绘图命令进行可视化。6. 常见问题排查与实战心得在实际操作中你一定会遇到各种报错和数据异常。以下是一些典型问题的排查思路问题merge命令后观测值数量异常增多远多于预期。原因最可能是合并键Stkcd和date不唯一。使用duplicates report Stkcd date分别检查主表和using表。解决在合并前务必确保两个数据集中Stkcd和date的组合是唯一的。如果有重复需要根据业务逻辑决定是去重duplicates drop还是汇总。问题使用bysort和_n-1向前填充时状态没有正确延续。原因数据没有严格按照Stkcd和date排序。bysort默认升序排列如果日期顺序混乱填充就会出错。解决在执行bysort Stkcd (date): ...之前先用sort Stkcd date或isid Stkcd date确保排序正确。isid命令可以同时检查是否唯一且已排序。问题从数据库导出的RiskWarning字段是字符串但包含空格、换行符等不可见字符导致判断失效。原因原始数据清洗不彻底。解决使用replace RiskWarning strtrim(stritrim(RiskWarning))去除首尾和中间多余空格。对于更复杂的字符可以用subinstr()函数替换。问题最终数据集中某些股票在已知的被ST期间is_risk_warning指标仍为0。排查检查原始事件表中该股票的ST生效日期是否正确录入。检查该股票在ST生效日是否处于上市状态是否已退市或尚未上市。检查冲突解决规则是否错误地覆盖了正确的事件数据。心得建立一个“已知案例测试集”。手动整理10-20家历史上典型ST公司的关键日期和状态在每一步数据处理后都筛选出这些公司进行比对这是验证数据流水线是否可靠的最直接方法。关于Stata版本与命令热词中提到了Stata 18。新版本通常有性能提升和新功能。例如frame命令可以更方便地管理多个数据集。但核心的数据处理逻辑merge,bysort,replace是通用的。对于“亚组分析”、“莫兰指数”、“核密度估计”等那是数据准备好之后的分析阶段与本项目的数据整理阶段是上下游关系。本项目产出的洁净数据正是为这些高级分析奠定可靠的基础。最后数据整理工作没有绝对的“完成”只有不断的“迭代”。交易所的规则在微调数据源也可能变化。因此将整个清洗流程封装成清晰的、模块化的Do文件至关重要。当需要更新到2024年数据时你只需要替换最新的原始数据文件重新运行整个Do文件就能高效地获得更新后的数据集。这套方法的价值不仅在于一份现成的数据更在于一套可复现、可审计、可持续更新的数据生产流程。