European Short Position Data

Data
Net short position disclosures under EU Regulation 236/2012, published by national regulators in the EU, the UK, Norway and Iceland
Sample
19 countries, position dates through September 2026 (update of September 28, 2026)
Code
Scraping and cleaning pipeline in two notebooks; the Norway download is the example below
Author
Qingyi (Freda) Song Drechsler

policy Background

Following the global financial crisis of 2008, European Securities and Markets Authority (ESMA) launched policy EU N236/2012 on short selling and certain aspects of credit default swaps. The purpose of this EU-wide reporting regulation is to increase market stability by reducing the opaqueness of market activities through mandatory daily reporting by institutional investors.

Therefore, each participating country’s financial monitoring agency requires market participants with the significant short positions on qualifying securities to self-report the positions, and further makes such information available to the public.

technical background

As each government agency’s website carries the data differently, and often the data scraping exercise involves mimicking the “clicking” action of a mouse, the package Selenium suits the need perfectly.

However, I would like to emphasize here that this code needs human monitoring over time, as government agency’s website can change layout or the presented data might show different format. So please do not rely on the code output blindly (more likely it will crash somewhere before getting to the end!).

Our student intern Shuai Zheng contributed to this code.

This code was revamped significantly to reflect the selenium package update as well as changes in the host countries’ websites.

Update: October 2026

the data

The tables and chart below summarize the combined dataset as of the latest update (September 28, 2026): every net short position disclosure published by the 19 regulators covered, with the position holder, the company (issuer and ISIN), the net short position as a percentage of issued share capital, and the position date.

Disclosures Companies (ISINs) Position holders First position date Last position date
Country
UK 112,510 941 621 2012-10-31 2026-07-09
Belgium 81,689 63 192 2012-10-31 2026-09-16
Germany 61,498 1,038 489 2003-03-26 2026-09-21
France 47,941 271 530 2012-10-03 2026-09-18
Sweden 39,732 465 486 2010-05-10 2026-09-21
Italy 25,705 232 307 2012-08-30 2026-09-21
Netherlands 24,705 99 281 2012-11-01 2026-09-21
Spain 13,743 90 254 2010-06-10 2026-09-18
Finland 10,939 80 239 2012-10-31 2026-09-21
Denmark 9,071 67 213 2012-11-01 2026-07-31
Norway 8,565 143 179 2020-02-24 2026-09-24
Austria 2,303 30 103 2012-11-01 2026-09-21
Poland 1,326 48 79 2012-11-01 2026-09-18
Ireland 945 27 75 2012-11-01 2026-05-29
Greece 407 22 25 2013-05-30 2026-09-18
Hungary 189 6 19 2012-11-05 2018-02-02
Luxembourg 62 2 5 2012-11-01 2017-11-20
Iceland 35 8 6 2017-07-07 2026-09-16
Czech 28 2 5 2017-04-19 2018-11-30
All countries 441,393 3,493 1,559 2003-03-26 2026-09-24

The chart counts the companies with at least one disclosure dated in each month, which is comparable across countries; raw disclosure counts are not, since some regulators (Belgium until 2022, for example) repeat unchanged positions every day.

the code

The code runs in two parts. Part 1 downloads each regulator’s raw disclosure file, using Selenium for sites that need clicking and plain HTTP requests where a file or API address is available. Part 2 reads each country’s file, standardizes the columns, runs validation checks, and appends the new records to the previous update, since some regulators do not keep their full history online.

The example below is the Norway download, which reads the regulator’s API directly instead of clicking through a website.

example: Norway

Norwegian Finanstilsynet presents the data in the json format through its API portal after the most recent website revamp, therefore the code does not resemble the other countries’ download process.

# import packages
import json

import certifi
import pandas as pd
import urllib3
http = urllib3.PoolManager(cert_reqs='CERT_REQUIRED',
                           ca_certs=certifi.where())
url = "https://ssr.finanstilsynet.no/api/v2/instruments"
r = http.request('GET', url)
assert r.status == 200, f"Norway API returned status {r.status}"

data = json.loads(r.data.decode('utf-8'))
df = pd.json_normalize(data, 'events', ["isin", "issuerName"],
                       max_level=0)
dfValid = df.loc[df.shares != 0]
print(f"Norway: {df.shape[0]} raw records, "
      f"{dfValid.shape[0]} with a nonzero position")
Norway: 3364 raw records, 3187 with a nonzero position
df.head()
date shortPercent shares activePositions isin issuerName
0 2026-10-01T00:00:00 0.00 0 [] BMG9156K1018 2020 BULKERS
1 2026-09-30T00:00:00 1.80 413635 [{'date': '2026-09-30T00:00:00', 'shortPercent... BMG9156K1018 2020 BULKERS
2 2026-09-29T00:00:00 1.64 377008 [{'date': '2026-09-29T00:00:00', 'shortPercent... BMG9156K1018 2020 BULKERS
3 2026-09-25T00:00:00 1.43 329408 [{'date': '2026-09-25T00:00:00', 'shortPercent... BMG9156K1018 2020 BULKERS
4 2026-09-24T00:00:00 1.09 249963 [{'date': '2026-09-24T00:00:00', 'shortPercent... BMG9156K1018 2020 BULKERS

The field activePositions is a list of dictionary items that report detail information on short sellers and the respective holdings, and one needs to further open up this field to have a long table that shows each item as a separate row.

While most records show only one item in the activePositions field (i.e. one position holder’s record), there are many cases where more than one position holder’s records are presented in the activePositions field, organized in a dictionary. And note that the variable shortPercent actually represents the aggregated shortPercent reported by each position holder in that row.

For instance, below is the content of one activePositions:

[{‘date’: ‘2023-03-03’,

‘shortPercent’: 0.68,

‘shares’: 153129,

‘positionHolder’: ‘Voleon Capital Management LP’},

{‘date’: ‘2023-03-07’,

‘shortPercent’: 0.89,

‘shares’: 198486,

‘positionHolder’: ‘WorldQuant LLC’}]

As some of the records report a complete empty activePositions field, I first remove these records from the entire sample and then parse out the position holder information from this field, and then create a new dataframe that contains all the relevant fields.

# parse out the positionHolder info from the activePositions column
dateLst, holderLst, shortPctLst = [], [], []
shareLst, isinLst, issuerLst = [], [], []

for i in range(dfValid.shape[0]):
    thisIsin = dfValid.iloc[i].isin.__self__.to_dict()['isin']
    thisIssuer = dfValid.iloc[i].issuerName
    for pos in dfValid.iloc[i].activePositions:
        holderLst.append(pos['positionHolder'])
        shortPctLst.append(pos['shortPercent'])
        dateLst.append(pos['date'])
        shareLst.append(pos['shares'])
        isinLst.append(thisIsin)
        issuerLst.append(thisIssuer)

longDf = pd.DataFrame({
    'position_date': dateLst, 'isin': isinLst, 'issuer': issuerLst,
    'position_holder': holderLst, 'net_short_position': shortPctLst,
    'NumPositions': shareLst,
})

Last step is to remove the redundant rows from longDf: the reason is that the json data reports aggregate value by date per row, with each row possibly containing more than one position_holder.

When at least one of the position holders change position, it will lead to update in the aggregate short position, even though the rest of the position holders in that row might not report any changes. With that background in mind, when expanding the position_holders one by one, the ones that do not change positions will get repopulated day after day and therefore appear in duplicate records.

# the API reports one row per date with possibly several position
# holders, and holders who didn't change position get repeated on every
# subsequent date - drop those duplicates
longDf = longDf.drop_duplicates()
longDf.shape
(3537, 6)

And the final output for Norway looks like the following:

longDf.sort_values(['isin', 'position_holder', 'position_date']).head()
position_date isin issuer position_holder net_short_position NumPositions
800 2025-09-30T00:00:00 BMG0451H2087 ARCHER WorldQuant LLC 0.52 477063
799 2025-10-02T00:00:00 BMG0451H2087 ARCHER WorldQuant LLC 0.66 599968
798 2025-10-06T00:00:00 BMG0451H2087 ARCHER WorldQuant LLC 0.70 701581
797 2025-10-10T00:00:00 BMG0451H2087 ARCHER WorldQuant LLC 0.81 807536
796 2025-10-24T00:00:00 BMG0451H2087 ARCHER WorldQuant LLC 0.78 783521