← back to /for-funds

Schema documentation

Two tables make up the data product: insidereu.filings (MAR Article 19 PDMR) and insidereu.substantial_holdings (Transparency Directive / DTR-5 / national major-holding notifications).

Schema stable since 2025-Q4; ingested_at added in 2026-Q1. No field has been renamed or dropped since. Other tables in the insidereu schema are application-internal and not part of the data product.

Table: insidereu.filings

PDMR (Person Discharging Managerial Responsibilities) and closely-related-person insider transactions filed under Article 19 of EU MAR (Regulation 596/2014) and UK/EEA equivalents.

ColumnTypeNullMeaning
idtext PKnoStable cross-scrape dedup key, format {country}-{source-system-ref}. Re-ingesting the same record is idempotent.
countrytextnoISO 3166-1 alpha-2 of the regulator's jurisdiction. Not the issuer's domicile.
insider_idtextnoStable insider key within a country. Cross-country join not guaranteed. Switzerland is the exception: the regulator publishes a role code, not a person, so CH keys are per role and issuer (see caveat 8).
insider_nametextnoAs reported by regulator. Nordic and other transliteration preserved. For CH it is synthetic, e.g. Board Member (Issuer AG). Some notices have no named person and read PDMR at <issuer>.
insider_roletextyesE.g. Chief Executive Officer, Member of the Board, Closely associated person, or the literal PDMR when no sub-role is provided.
company_nametextnoIssuer name.
isintextyesCoverage by country varies — see the coverage matrix. When null, join on (country, company_name).
issuer_leitextyesGLEIF Legal Entity Identifier of the issuer, resolved from isin. Null where GLEIF has no mapping or the ISIN is missing.
tickertextyesYahoo Finance symbol, populated for about three quarters of rows. Resolved from the ISIN and accepted only if the exchange suffix matches the ISIN's country; existing values are never overwritten. UK rows from Investegate carry the raw LSE code (e.g. HAS), not a Yahoo symbol.
exchangetextyesReporting venue.
transaction_datedatenoDate the trade was executed.
filing_datedatenoThe date the market could first see the filing, where the regulator gives one: SE, DE, FI, DK and, for UK rows ingested from 2026-09-21, the RNS publication date. For most other countries it is the transaction date or the announcement date, and CH rows are flagged raw_data.filing_date_is_trade_date. Do not use it as a point-in-time stamp without reading caveat 2.
transaction_typetextnoCHECK IN ('buy','sell','unknown'). 'unknown' means either the source carried no parseable direction, or the transaction is not an open-market trade (an award, grant, vesting or pledge release, flagged raw_data.non_market). Filter transaction_type IN ('buy','sell') for direction-confirmed rows only.
sharesnumericyesVolume of the trade. Set to NULL where the regulator's figure was found to be unreliable (raw_data.figures_suspect).
pricenumericyesPrice per share in currency. For SE and CH the scraper converts the regulator's SEK / CHF price at a fixed rate and labels the row EUR; the original is kept in raw_data. NULL where the figure failed a plausibility check (caveat 9).
value_eurnumericyesTrade size in EUR, computed at ingestion with a fixed approximate rate per currency (SEK 0.087, NOK 0.085-0.087, DKK 0.134, CHF 1.04, GBP 1.17, USD 0.92), not the ECB rate on the transaction date. Adequate for ranking and screening, not for precise P&L.
currencytextyesISO 4217 of price. EUR for DE, CH, SE (converted) and Finland; DKK, NOK and GBP where the source price is kept native.
source_urltextyesDeep link to the regulator's original filing. Always populated for live-scraped rows.
raw_datajsonbyesSource-system payload plus provenance flags added by our pipeline. Keys are listed under raw_data keys.
scraped_attimestamptzyesWhen the scraper first hit the source page.
current_price_eurnumericyesMost recent close, refreshed nightly for active tickers.
price_refreshed_attimestamptzyesWhen current_price_eur was last refreshed.
ingested_attimestamptznoWhen the row landed in our DB. Use this for ingestion-velocity / latency analysis.

Dedup recommendation

A server-side enrichment-aware view is shipped: read from insidereu.filings_dedup rather than the base table. It collapses two enriched rows from different sources with matching (country, issuer-key, txn_date, insider, direction, shares, price) and signal-only rows whose enriched sibling exists in another source. It does not collapse two signal-only rows with no enriched sibling. Same schema, no extra columns. The public REST API and sample CSV exports already read from the view.

Awards, grants and other non-market transactions

Share-based awards, option grants and exercises, vestings and pledge releases are not mapped to buy or sell. Where the source labels them (FI, DK, GB) the row keeps transaction_type = 'unknown', with raw_data.non_market = true and the regulator's wording in raw_data.nature_raw. The Swedish and German feeds drop these natures at ingestion. In Finland more than half of PDMR notices are share-award receipts, which is why its buy/sell share is low. Some rows still carry price = 0 (for example French award notices); filter price > 0 to exclude them.

Swedish rows also carry raw_data.linked_to_programme = true when the regulator flags the transaction as linked to a share-option or incentive programme, so discretionary purchases can be separated from programme ones.

NULL ISIN handling

Across all rows ISIN coverage is about 93%, but that is driven by Sweden (99%), Switzerland (100%) and Germany (100%). Of the 26 countries with rows in the last 90 days, 11 have under 50% (measured 2026-09-21): NO, GR, HU, IT, BE, AU, LU, CY, MT, IS and SI. About 2,300 ISINs were filled by matching the company name to other rows of the same company in the same country when exactly one ISIN matched; those rows carry raw_data.isin_source = "company_name_propagation". When isin IS NULL, join on (country, company_name) against a reference universe.

raw_data keys

Beyond the source payload, our pipeline adds these keys. Absence of a flag means it did not apply or has not been evaluated for that row.

non_markettrue when the transaction is an award, grant, vesting, option exercise or pledge release, not an open-market trade. transaction_type stays unknown.
nature_rawThe regulator's own wording for the nature of the transaction, verbatim (FI, DK, GB, and Swedish rows ingested from 2026-09-21).
linked_to_programmeSE only: true when the regulator flags the transaction as linked to a share-option or incentive programme.
filing_date_is_trade_dateCH: SIX exposes no publication date, so filing_date equals transaction_date.
published_dateGB (Investegate): the RNS publication date shown in the announcement list, used as filing_date.
tx_date_from_publicationGB: the article body could not be parsed, so transaction_date is the publication date, not the trade date.
isin_sourceSet to company_name_propagation when the ISIN was inferred from other rows of the same company.
signal_onlytrue when only a headline-level record exists (issuer, date, link) and no detail page or PDF has been parsed.
detail_enriched_atWhen the detail page or PDF was parsed for this row.
figures_repairedThe stored price was the trade total entered as a unit price; corrected as total / shares. Holds the original figures.
figures_suspectThe price failed a plausibility check (more than 20 times the issuer's own median in a 180-day window, or an implausible unit) and price / value_eur were set to NULL. Holds the original figures.
figures_nulledGB: shares, price and value were parsed from the wrong part of the announcement and were set to NULL.

History windows

Earliest transaction date held per country, as of 2026-09-21. Where a regulator caps its own history, that is the limit; we cannot go further back.

CountryEarliestRowsLimit
SE2021-01-0166,898Walked in single-day windows; the 34 weekdays without rows are Swedish public holidays.
CH2023-09-216,708SIX keeps about three years.
GB2024-093,957Depends on the LSE and Investegate feeds; the daily job runs six times each weekday from 2026-09-21.
DE2025-04-153,640BaFin's portal holds nothing earlier.
FI2018-112,401Sparse before 2025; the Nasdaq news API returns roughly the latest 10,000 items.
AT2023-071,837Headline-level only.
NO, ES, FR, DK and others2025-12 to 2026-05under 1,800 eachCoverage began when each scraper was built; see the coverage matrix.

Sample row

{
  "id": "se-fi-example-person__example-ab-2026-05-16-4562-91246",
  "country": "SE",
  "insider_id": "example-person__example-ab",
  "insider_name": "Example Person",
  "insider_role": "Chief Executive Officer",
  "company_name": "Example AB",
  "isin": "SE0000000000",
  "ticker": "EXMPL-B.ST",
  "exchange": "Nasdaq Stockholm",
  "transaction_date": "2026-05-16",
  "filing_date": "2026-05-19",
  "transaction_type": "buy",
  "shares": 2000,
  "price": 45.62,
  "value_eur": 91246,
  "currency": "EUR",
  "source_url": "https://marknadssok.fi.se/Publiceringsklient/en-GB/Search/Search",
  "raw_data": {
    "original_currency": "SEK",
    "original_price": 524.4,
    "linked_to_programme": false,
    "nature_raw": "Acquisition"
  },
  "scraped_at": "2026-05-20T02:30:35.068+00:00",
  "ingested_at": "2026-05-20T02:30:35.068+00:00"
}

Table: insidereu.substantial_holdings

Transparency Directive (Directive 2004/109/EC) major-holding notifications and national equivalents — DTR 5 in the UK, BaFin §33-§38a WpHG in Germany, AFM in NL, CNMV in ES. Triggered when any holder crosses 3% / 5% / 10% / 15% / 20% / 25% / 30% / 50% / 75% of voting rights in a listed issuer.

ColumnTypeNullMeaning
idtext PKnoStable per-source key.
countrytextnoISO 3166-1 alpha-2 of the regulator that received the notification.
filer_nametextnoNotifying entity (institutional holder, individual, syndicate).
filer_idtextnoStable filer key within a country.
issuer_nametextnoThe company whose voting rights crossed a threshold.
issuer_isintextyesISIN. Coverage by country varies — see audit.
issuer_leitextyesGLEIF LEI of the issuer, resolved from issuer_isin. Null where no ISIN or no GLEIF mapping.
issuer_tickertextyesYahoo Finance ticker (derived).
filing_datedatenoDate the regulator published the notification.
event_datedateyesDate the threshold was crossed. Often 1-3 business days before filing_date. ES currently inverted (audit § 4a).
threshold_pctnumericyesThreshold crossed, in percent. Currently 0% populated except NO (17%). Full population on the roadmap.
directiontextnoCHECK IN ('crossed_above','crossed_below','disclosed'). ~99% of rows are disclosed today. Reliable directional values only for CH (98%) and NO (1.9%).
new_pctnumericyesFiler's voting rights percentage after the event.
previous_pctnumericyesFiler's voting rights percentage before the event.
voting_rights_countbigintyesAbsolute count of voting rights held after the event.
regulationtextyesThe exact regulatory citation — e.g. Section 33 WpHG, DTR 5.1.2 R, AFM 5:38 Wft.
source_urltextyesDeep link to the original notification.
raw_datajsonbyesVerbatim source payload.
created_attimestamptznoEquivalent to filings.scraped_at.
ingested_attimestamptznoWhen the row landed in our DB. Use for ingestion-velocity analysis.

Dedup

Natural key: (country, filer_id, issuer_isin OR issuer_name, event_date, threshold_pct, direction). The id PK dedupes within a single source. No multi-source overlap observed today (each regulator publishes exactly once).

direction = 'disclosed'

Three regulators (FSMA-BE, AMF-FR, AFM-NL) publish “current holdings disclosure” notices that don't state whether the filer crossed up or down — they only restate the new position. Those rows carry direction = 'disclosed'. For strict directional whale events, filter direction <> 'disclosed'.

Sample row

{
  "id": "DE-BAFIN-2026-05-19-77881",
  "country": "DE",
  "filer_name": "BlackRock, Inc.",
  "filer_id": "DE-BAFIN-FILER-BLACKROCK",
  "issuer_name": "Volkswagen AG",
  "issuer_isin": "DE0007664039",
  "issuer_ticker": "VOW3.DE",
  "filing_date": "2026-05-19",
  "event_date": "2026-05-15",
  "threshold_pct": null,
  "direction": "disclosed",
  "new_pct": 5.12,
  "previous_pct": 4.98,
  "voting_rights_count": 15125400,
  "regulation": "Section 33 WpHG",
  "source_url": "https://www.bafin.de/.../77881.pdf",
  "ingested_at": "2026-05-20T02:31:13.314+00:00"
}

Known caveats — read before integrating

  1. transaction_type is ternary. 'unknown' flags rows where the source did not carry parseable direction (signal-only). Filter transaction_type IN ('buy','sell') for direction-confirmed rows only. Fully reliable buy/sell mix today: CH, DE, SE; smaller reliable tails: DK, FI, GB, IT, NO, ES.
  2. filing_date is a real publication date only for some countries. The regulator's publication date is used for SE, DE, FI and DK, and for UK rows from Investegate ingested from 2026-09-21. For CH it equals the transaction date (SIX publishes none; flagged raw_data.filing_date_is_trade_date). In the last 90 days it equals the transaction date for nearly every other country, and for UK rows before 2026-09-21 it is the transaction or ingestion date. A backtest that enters at filing_date will look ahead for those rows. ingested_atis reliable for “when we knew about it” on live-ingested rows only; rows loaded in a historical backfill carry the backfill date.
  3. threshold_pct is sparsely populated in holdings. Until full backfill, the most reliable holdings signal is the change new_pct - previous_pct.
  4. ISIN coverage is non-uniform. Tier 1-2 countries are >99%; Tier 4 countries can be 0%. See per-country detail in the coverage matrix →
  5. Dedup is applied server-side via insidereu.filings_dedup. Read from the view, not the base table.
  6. current_price_eur is refreshed nightly for tickers with confirmed Yahoo Finance mapping. Coverage tracks ticker coverage.
  7. Non-market transactions (awards, grants, vests, pledge releases) are not mapped to buy or sell; see the section above. Filter price > 0 to drop zero-price rows.
  8. Swiss insiders are not individuals. SIX publishes a role code (board, executive, closely associated), not a name, so CH insider_name is Board Member (Issuer) and insider_id is per role and issuer. Counting distinct insiders (for example cluster detection) is not possible for CH; issuer-level analysis is.
  9. Some source figures are wrong at the regulator and are corrected or blanked. Where a trade total was entered as a per-share price (for example a price 1.7 million times the issuer's normal level) the row is repaired if total / shares matches the issuer's usual price, otherwise price and value_eur are set to NULL and the original is kept in raw_data.figures_repaired or raw_data.figures_suspect. Around 190 Swedish, Swiss and other rows were handled this way on 2026-09-21. The check compares each price with the same issuer's trades within 180 days, so a few wrong figures on thinly traded issuers can remain. A handful of very large values (above EUR 1 billion) are genuine corporate actions, such as takeovers; treat single trades above EUR 100 million with care.
  10. value_eur uses fixed FX.See the column definition. Do not use it as an exact P&L figure.
  11. Programme purchases. Most feeds do not separate discretionary purchases from share-scheme or incentive-programme ones. Only Sweden flags this (raw_data.linked_to_programme), and only from the data re-pulled on 2026-09-21. A “cluster” of managers buying may be several of them joining one programme.
  12. History depth differs sharply by country. See the history windows table. Only Sweden has more than three years.
  13. insidereu.prices is an internal cache of daily closes used for our own analysis and is not part of the data product; prices are not redistributed.

API access

Filings and holdings are available via three delivery surfaces:

  • REST: https://insidereu.com/api/v1/filings?country=DE&since=2026-05-01
  • CSV / Parquet drop: nightly to S3-compatible bucket (set up on request, pilot tier and above).
  • Postgres read replica: enterprise tier.

API keys are provisioned per-seat via insidereu.api_keys with hashed key storage.