skills/wrds/SKILL.md
Use when "query WRDS", "pull SEC filings", "access Compustat/CRSP/ExecuComp/Capital IQ", "Form 4 insider data", "13F institutional ownership (Thomson)", "13D/13G blockholders", "ISS governance/compensation/voting/directors", "proxy advisor recommendations", "TAQ intraday/NBBO", "SDC M&A or new issues", "DealScan syndicated loans", "PitchBook PE/VC deals", "FISD corporate bonds", "municipal bonds / muni trades / MSRB RTRS / SDC municipals", "Form D/ADV", "fund formation", "FJC court data", "linking datasets / join keys (gvkey-permno via CCM, cik-gvkey via wciklink, DealScan-Compustat)", or any WRDS PostgreSQL query or SAS ETL on the WRDS grid (qsub/qsas/SGE).
npx skillsauth add edwinhu/workflows wrdsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Building a proxy-voting panel? Use the
npx-ownership-panelskill, not this one. It ownsrisk.voteanalysis_npx(238M rows / 329 GB), the ISS->CRSP fund crosswalk, and the four-leg SGE pipeline that produces the analysis-ready panel. This skill covers WRDS access patterns generally.
ALWAYS write an SGE submission script and submit via qsub. No exceptions.
ssh wrds 'cat files.tsv | ./parser > output.tsv' → WRONG. Use qsub.ssh wrds 'nohup ./process &' → WRONG. Still the login node. Use qsub.ssh wrds 'python3 bulk_process.py' → WRONG. Use qsub.qsub -t 1-20 submit.sh → CORRECT.The login node is for: qsub, qstat, qdel, scp, ls, head, short psql queries.
Submission patterns and working array jobs: references/edgar.md (§ SGE index build), scripts/sec_index_rga/submit_array.sh, scripts/parse_13f/sge/submit_array.sh, and ../npx-ownership-panel/scripts/run_pipeline.sh.
</EXTREMELY-IMPORTANT>
Running compute on the login node is NOT HELPFUL — it gets the user's account flagged, the job killed, and the work lost. You run on the login node because qsub feels like overhead. The overhead is 5 minutes of script writing. The downside is account suspension and a rerun from scratch.
qsub -t 1-1 submit.sh. The login-node "quick test" is the run that flags the account — one file becomes 100K when the command changes, and 173K filings over NFS is not 30 seconds.submit_quorum.sh. Citing it as login-node precedent is an unverified claim presented as fact.wrds_clean_filings path convention is cik_int.zfill(10)[:6]/{cik_int}/{accession}.txt (see references/edgar.md). Hand-rolled path logic gets this wrong.scan_covers profiles handle header extraction, body parsing, and custom extractors (Custom field type) — "this parser is different enough to need its own binary" has not yet been true once.ssh wrds '... | ./binary > output' → STOP. That's login-node compute. Write a submit script.ssh wrds 'nohup ... &' → STOP. nohup doesn't change the node. Use qsub.ssh wrds 'python3 ...' for anything that reads >10 files → STOP. Use qsub.references/edgar.md before building a new WRDS file parser → STOP. The path conventions, SGE patterns, and existing parsers are already documented. Read them first.scripts/scan_covers/ is a generic profile-based framework. Add a profiles_*.go file, not a new binary. The framework handles SGE sharding, path construction, concurrency, and form-type filtering.scripts/scan_covers/ → STOP. This framework exists precisely so you don't reinvent extraction infrastructure. Every standalone parser is technical debt that should have been a profile.scripts/scan_covers/ — generic profile-based Go framework with SGE, concurrency, path handlingprofiles_*.go file — not a standalone binary. The Profile struct supports pattern-based fields AND custom extractors (set FullBody: true for body-text searches like prospectus 485 filings — see profiles_proxy_advisors.go)references/edgar.md — path conventions, existing profiles, SGE submission patternsBuilding a standalone parser when scan_covers exists is NOT HELPFUL — it reinvents infrastructure that already handles SGE sharding, NFS concurrency, path construction, form-type filtering, and error handling. You built a 300-line standalone Go binary, ran it on the login node, got the path convention wrong, and spent 5 iterations fixing it. Adding a 60-line profile to scan_covers would have worked on the first try.
Every standalone EDGAR parser is technical debt. The scan_covers framework exists to eliminate this class of mistake.
</EXTREMELY-IMPORTANT>
WRDS (Wharton Research Data Services) provides academic research data via PostgreSQL at wrds-pgdata.wharton.upenn.edu:9737.
Before executing ANY WRDS query, you MUST:
This is not negotiable. Skipping sample inspection is NOT HELPFUL — the user builds analysis on data with undetected quality problems.
.head()/.sample() first; query success ≠ data quality.Before EVERY query execution:
For Compustat queries (comp.funda, comp.fundq):
indfmt = 'INDL'datafmt = 'STD'popsrc = 'D'consol = 'C'For CRSP v2 queries (crsp.dsf_v2, crsp.msf_v2):
sharetype == 'NS'securitytype == 'EQTY'securitysubtype == 'COM'usincflg == 'Y'issuertype.isin(['ACOR', 'CORP'])For Form 4 queries (tr_insiders.table1):
For ALL queries:
.head() or .sample() BEFORE claiming successWriting SAS code that forces full table scans when indexes exist is NOT HELPFUL — the user's job runs 100x slower than necessary and may timeout. </EXTREMELY-IMPORTANT>
Before EVERY SAS program execution:
For probing inputs (do this FIRST — metadata only, seconds):
PROC CONTENTS data=lib.x varnum on every input — variables, types, lengths, formats$6/$8 gvkey = silent zero matches)PROC SQL; select memname, nobs from dictionary.tables where libname='LIB'; — row counts before committing to the jobPROC PRINT data=lib.x(obs=20); var ...; — values look like the docs claim (always obs=, always var)PROC DATASETS library=scratch; — inventory intermediates; delete there, not via a rewriting DATA stepFor merges/joins:
PROC SORT + DATA merge)defineKey/defineData/defineDone pattern correctlyh.output() uses double quotes for macro resolution (not single quotes)call missing() initializes hash data variables for non-matchesFor WHERE clauses (CRITICAL):
year(date), month(date), datepart(dt) wrapping indexed columnsBETWEEN "01jan&year."d AND "31dec&year."d range patternupcase(), substr() on indexed columnsyear() = X AND quarter() = Y)For batch processing:
#$ -t start-end) not sequential loop-sysparm (not -set or %sysget)#$ -l m_mem_free=4G minimum)For PROC SQL:
calculated keyword used for computed column references in HAVINGFor macros:
&year. not &year)options mprint mlogic symbolgen used during developmentPROC SORT + MERGE and need no sorting; PROC SQL still sorts for joins. The hash is 5 extra lines — choosing sort-merge for a lookup join makes the user's job slower for your convenience.year(date) (or any function) on an indexed column forces a full table scan over millions of rows; BETWEEN with date literals uses the index.h.output(dataset: '...') block macro resolution — the output dataset name comes out wrong. Always double quotes.%sysget is unreliable under SGE — it may return blank silently. Pass the year via -sysparm + &sysparm..where year(date) = anything → STOP. Use BETWEEN with date literals.proc sort; data; merge for a lookup join → STOP. Use hash object.%do year = start %to end loop → STOP. Use SGE array job.h.output(dataset: '...') → STOP. Use double quotes.-set or %sysget for SGE task parameters → STOP. Use -sysparm.See references/sas-etl.md for complete patterns:
dictionary.tables)| Dataset | Schema | Key Tables |
|---------|--------|------------|
| Compustat | comp | company, funda, fundq, secd |
| ExecuComp | comp_execucomp | anncomp |
| CRSP | crsp | dsf, msf, stocknames, ccmxpf_lnkhist |
| CRSP v2 | crsp | dsf_v2, msf_v2, stocknames_v2 |
| Form 4 Insiders | tr_insiders | table1, header, company |
| ISS Incentive Lab | iss_incentive_lab | comppeer, sumcomp, participantfy |
| Capital IQ | ciq | wrds_compensation |
| IBES | tr_ibes | det_epsus, statsum_epsus |
| Form D / Reg D | wrdssec | wrds_vc_formd (parsed, 2000–2020); index: wrdssec_all.forms (all CIKs) or wrds_forms (filer only) — default to forms, see references/wrds-forms-tables.md |
| SEC EDGAR | wrdssec_all | forms (raw index, all CIKs per filing — default), wrds_forms (filer-only view), wciklink_cusip |
| SEC Search | wrds_sec_search | filing_view, registrant |
| EDGAR | edgar | filings, filing_docs |
| Fama-French | ff | factors_monthly, factors_daily |
| LSEG/Datastream | tr_ds | ds2constmth, ds2indexlist |
| FJC (Federal Judicial Center) | fjc | civil, criminal, bankruptcy, appeals |
| FJC Linking | fjc_linking | wrds_civil_link, wrds_criminal_link |
| SDC New Issues (IPO/SEO/Debt) | tr_sdc_ni | wrds_ni_details — equity + debt offerings |
| SDC Mergers & Acquisitions | tr_sdc_ma | wrds_ma_details — M&A transactions |
| TAQ Legacy | taq | mast_YYYY, wrds_iid_YYYY — second-level (1993–2006) |
| TAQ Millisecond | taqmsec | mastm_YYYY, wrds_iid_YYYY, ctm_YYYYMM, complete_nbbo_YYYYMMDD |
| Thomson S12 (Mutual Fund Holdings) | tfn (SAS) / tr_mutualfunds (PG) | s12 — 13F/N-CSR fund holdings |
| Thomson S34 (13-F Institutional) | tfn (SAS) / tr_13f (PG) | s34 — 13-F institutional holdings |
| FISD / Mergent (Corporate Bonds) | fisd_fisd | fisd_mergedissue, fisd_mergedissuer — corporate/agency/Treasury; NOT the muni source (issuer_type='M' munis are incidental) |
| Municipal trades (MSRB RTRS) | msrb | msrb (trades + inline CUSIP master: coupon, maturity), msrb_lookup; also msrb_all, msrbsamp. Primary muni source. See references/muni-bonds.md |
| Municipal new issues (SDC) | tr_sdc_municipals | deal-level: ratings, GO/rev, bank-qualified, callable, size, sector — but SELECT is permission-denied on this subscription (not licensed); msrb is the only readable muni schema. See references/muni-bonds.md |
| PitchBook | pitchbk_companies_deals, pitchbk_investors_funds_lps, pitchbk_fund_returns | deal, company, fund, wrds_fund_returns — dealsize in USD millions |
Initialize PostgreSQL connection to WRDS:
import psycopg2
conn = psycopg2.connect(
host='wrds-pgdata.wharton.upenn.edu',
port=9737,
database='wrds',
sslmode='require'
# Credentials from ~/.pgpass
)
Configure authentication via ~/.pgpass with chmod 600:
wrds-pgdata.wharton.upenn.edu:9737:wrds:USERNAME:PASSWORD
Connect via SSH tunnel:
ssh wrds
This uses ~/.ssh/wrds_rsa for authentication.
Always include for clean fundamental data:
WHERE indfmt = 'INDL'
AND datafmt = 'STD'
AND popsrc = 'D'
AND consol = 'C'
Equivalent to legacy shrcd IN (10, 11):
df = df.loc[
(df.sharetype == 'NS') &
(df.securitytype == 'EQTY') &
(df.securitysubtype == 'COM') &
(df.usincflg == 'Y') &
(df.issuertype.isin(['ACOR', 'CORP']))
]
WHERE acqdisp = 'D' -- Dispositions
AND trancode IN ('S', 'D', 'G', 'F') -- Sales, Dispositions, Gifts, Tax
Always use parameterized queries (never string formatting):
Use scalar parameter binding for single values:
cursor.execute("""
SELECT gvkey, conm FROM comp.company WHERE gvkey = %s
""", (gvkey,))
Use ANY() for list parameters:
cursor.execute("""
SELECT * FROM comp.funda WHERE gvkey = ANY(%s)
""", (gvkey_list,))
Detailed query patterns and table documentation:
references/compustat.md - Compustat tables, ExecuComp, financial variablesreferences/crsp.md - CRSP legacy (SIZ) stock data and CCM linking${CLAUDE_SKILL_DIR}/../../skills/crsp-v2/SKILL.md - CRSP CIZ / v2 format (required for any data after 2024-12-31)references/insider-form4.md - Thomson Reuters Form 4, rolecodes, insider typesreferences/iss-compensation.md - ISS Incentive Lab, peer companies, compensationreferences/formd.md - Form D / Reg D (canonical): two sources (WRDS wrds_vc_formd + SEC EDGAR TSV/XML), grain & keys, denormalization gotcha, exemption + industry codes, post-2020 gap, validated benchmarksreferences/edgar.md - SEC EDGAR filings, URL construction, DCN vs accession numbersreferences/connection.md - Connection pooling, caching, error handlingreferences/taq.md - TAQ: master files, IID, raw tick processing (NBBO, VWAP, closing auctions), CRSP–TAQ merge, era transition (legacy vs millisecond)references/sas-etl.md - SAS metadata probing (PROC CONTENTS/DATASETS/PRINT), hash objects, index-friendly WHERE, SGE array jobs, PROC SQL optimizationreferences/postgres-vs-sas.md - Decision guide: when to use PostgreSQL vs SAS for WRDS ETL (benchmarks, constraints, hybrid pattern)references/fjc.md - FJC Integrated Database: civil/criminal case data, NOS codes, securities litigation queries, firm linkingreferences/sdc-issuances.md - SDC New Issues: IPOs, SEOs, 144A equity, debt offerings — schema discovery, cleaning filters, CRSP/Compustat linkingreferences/fisd-bonds.md - FISD/Mergent: corporate bond issuances, IG vs HY, 144A vs registered, rating classification, TRACE linkingreferences/sdc-ma.md - SDC M&A: deal counts, PE/LBO vs strategic buyer, deal status codes, public vs private targetreferences/fund-formation.md - Fund formation: Form D (pooled investment funds), EDGAR N-2 (closed-end fund IPOs), Form ADV (RIA registrations)references/pitchbook.md - PitchBook: schema architecture, dealsize/fundsize in USD millions, dealdate outliers, CIK crosswalk, fund performance (wrds_fund_returns), PE/VC/fund formation patternsreferences/proxy-advisors.md - Proxy-advisor customer identification: 485BPOS/485APOS body scan for ISS/Glass Lewis/Egan-Jones name variants; CRSP MFDB lift to mgmt_cd × year; validates against chongshu published CSVreferences/linkage.md - Cross-dataset linkage map: which identifiers are spines, the load-bearing link tables (CCM, wciklink, dswslink, MFDB), a "how do I join X to Y" table, and which vendor ids never crossreferences/blockholders.md - 13D/13G blockholder panel: Volkova replication, position %, the four mutually-exclusive holder flagsreferences/execucomp.md - ExecuComp: CEO anncomp, legacy codirfin vs current directorcomp, firm-year aggregationreferences/iss-directors.md - ISS Directors: risk.directors + risk.rmdirectors, type harmonization, 1996 gender backfill, S&P 1500 filterreferences/iss-voting.md - ISS Voting Analytics: vavoteresults, voteanalysis_npx, base-conditional turnout/forpct, agenda codesreferences/tfn-ownership.md - Thomson 13-F (S34) institutional ownership and S12 mutual-fund holdings via MFLINKS, passive/index classification, and Known Data Defects (D1-D9: split mis-adjustment, post-2013 coverage collapse, 2017Q4 S12 feed change, 13F value unit break, and two that are yours not the vendor's — D8 silent Int8 date overflow, D9 ownership above 100%). Read the defects section before trusting any split-era or post-2013 quarter.
scripts/ownership_dq.py (14 detectors, S12 and S34) — run these against any holdings panel before analysis. Tests: tests/ownership_dq_test.py (79 assertions, stdlib only).detect_calendar_bucket_gap on every reference/dimension table at build time, not just on the output panel. It is the one detector that catches a root cause rather than a symptom: a reference table missing a whole calendar bucket makes every downstream join fall back to a default, silently, and the result looks like a vendor defect (see D8).references/lpc-dealscan.md - LPC DealScan: legacy vs 2021+ flat schema, borrower ids, the gvkey link and its grain caveatsreferences/muni-bonds.md - Municipal bonds: MSRB RTRS trades, SDC municipalsreferences/wrds-forms-tables.md - wrdssec_all.wrds_forms and friends: filing metadata tables and their columnsWorking code from real projects:
examples/form4_disposals.py - Insider trading analysis (from SVB project)examples/wrds_connector.py - Connection pooling patternexamples/formd_regd.ipynb - Form D / Reg D: dedup validation, SEC TSV download, exemption trend chartsexamples/sdc_issuances_eda.ipynb - SDC New Issues: annual IPO/SEO/debt counts, 144A share, IG vs HY breakdownexamples/sdc_ma_eda.ipynb - SDC M&A: annual deal counts, PE/LBO vs strategic, public vs private target trendsexamples/fund_formation_eda.ipynb - Fund formation: Form D 3C.1/3C.7 counts, EDGAR N-2 closed-end fund IPOs, Form ADV RIA registrationsexamples/pitchbook_eda.ipynb - PitchBook: PE deal activity, VC rounds by stage, fund formation by vintage, IRR/TVPI by strategynpx-ownership-panel SKILL (promoted out of this skill's examples) - the full meeting-level proxy-voting x ownership panel: ISS N-PX fund votes reduced to (item x block) cells on the grid, joined to 13-F institutional and MF holdings. One bash command, verified end to end on 2026-07-25. Also carries the ISS->CRSP fund crosswalk. Use it for any N-PX or fund-level voting work.examples/blockholders_pipeline/ - 13D/13G → Volkova blockholder panel, end-to-end Python. redo_bridge.py is the reference implementation of TR personid → SEC rptOwnerCik name bridging (97.4% hit rate).examples/form4_pipeline/ - Two parallel Form 3/4/5 pipelines: the annualized SAS ownership panel and the XML owner bridge built from the raw filings.examples/proxy_advisors_pipeline/ - 485BPOS/485APOS scan for ISS / Glass Lewis / Egan-Jones customer relationships via the scan_covers Go framework + SGE.examples/fjc_eda.ipynb - FJC Integrated Database: securities cases (nos = 850), filing trends, court distributionexamples/lpc_dealscan_eda.ipynb (paired script: examples/lpc_dealscan_eda.py) - LPC DealScan: ~171K US facilities 1990-2020 (the normalized facility table; queries are capped at 2020-12-31), volume by year, loan type and purpose mixexamples/voting_ownership_eda.py - Standalone Python/PostgreSQL EDA of the same ISS-votes + ownership merge. For production work use the npx-ownership-panel skill, which is the SGE-ready, verified-end-to-end version of this analysis.scripts/test_connection.py - Validate WRDS connectivityscripts/inventory_schemas.py - Inventory every accessible WRDS PostgreSQL schema, its tables, and row counts — run this before guessing at a table namescripts/scan_covers/ - Generic profile-based Go framework for EDGAR extraction (SGE sharding, NFS concurrency, path construction, form-type filtering). Add a profiles_*.go, never a new standalone binary — see the Iron Law above.scripts/parse_13f/, scripts/scan_headers/, scripts/sec_index_rga/ - Companion EDGAR tooling: 13F table parsing, SEC header scanning, index buildingWRDS-provided samples at ~/resources/wrds-code-samples/:
ResearchApps/CCM2025.ipynb - Modern CRSP-Compustat mergeResearchApps/ff3_crspCIZ.ipynb - Fama-French factor constructioncomp/sas/execcomp_ceo_screen.sas - ExecuComp patternsWhen querying historical data, leverage current date context for dynamic range calculations.
Current date is automatically available via datetime.now(). Apply this to:
Implement dynamic date ranges in queries:
from datetime import datetime, timedelta
# Query last 5 years of data
end_date = datetime.now()
start_date = end_date - timedelta(days=5*365)
query = """
SELECT * FROM comp.funda
WHERE datadate BETWEEN %s AND %s
"""
df = pd.read_sql(query, conn, params=(start_date, end_date))
Always incorporate current date awareness in date-dependent queries to ensure results remain fresh across time.
development
Build the meeting-level proxy-voting × ownership panel on the WRDS SGE grid — ISS N-PX fund votes reduced to (item × block) direction cells, joined to institutional and mutual-fund ownership. Use when working with risk.voteanalysis_npx, N-PX fund-level votes, ISS→CRSP fund linking, index/passive/active voting blocks, or a proxy-voting panel that needs ownership attached.
development
Use when "CRSP CIZ", "CRSP v2", "CRSP flat file format 2.0", "crsp.dsf_v2 / msf_v2", "StkDlySecurityData", "StkMthSecurityData", "StkSecurityInfoHist", "stocknames_v2", "DlyRet / MthRet / DlyPrc / MthPrc", "SHRCD or EXCHCD equivalent in new CRSP", "SIZ to CIZ migration", "CRSP data after 2024", "CRSP delisting returns", "CRSP cumulative adjustment factors", "CRSP index INDNO / INDFAM", or any CRSP stock/index query where the legacy SIZ column names no longer exist.
development
Use when linking or deduping datasets by entity name rather than a shared key — 'fuzzy match', 'fuzzy name matching', 'entity resolution', 'record linkage', 'match company/person names', 'dedupe entity names', 'name-based join', 'bridge identifiers' (CIK ↔ permno ↔ gvkey ↔ wficn ↔ EIN ↔ personid), or any use of char n-gram TF-IDF, cosine similarity on names, `sparse_dot_topn`, or RapidFuzz at scale.
development
Use when building a publication-quality table in Python — 'regression table', 'results table', 'summary statistics table', 'etable', 'coefplot', 'great_tables', 'GT', 'gt table', 'format a table for the paper', 'export table to LaTeX/HTML', significance stars, spanners, or column formatting for a table headed into a paper, slide deck, or notebook.