
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文件就能高效地獲得更新后的數據集。這套方法的價值不僅在于一份現成的數據更在于一套可復現、可審計、可持續更新的數據生產流程。