justetf-scraping icon indicating copy to clipboard operation
justetf-scraping copied to clipboard

The ETFs with multiple tickers

Open ffffuturexu opened this issue 11 months ago • 8 comments

Thanks for this excellent work!

For the ETFs with multiple tickers in different exchanges, the current code seems to use ISIN as the unique identifier and only provides one ticker. This would raise some inconvenience if I want to query for information by ticker if that ticker is not in the overview df.

Do you consider adding a column to store all the tickers for one ETF?

ffffuturexu avatar Jan 11 '25 20:01 ffffuturexu

Hi @ffffuturexu, do you have any example, so I could check the behavior?

druzsan avatar Jan 13 '25 10:01 druzsan

For example, I am interested in this ETF Global X Nasdaq 100 Covered Call UCITS ETF D. It has three tickers (QYLD, QYLE, and QYLP) in different exchanges. While the overview df only stores QYLE in the column ticker.

image

In this case, if I want to query this ETF by ticker QYLD from the overview df, nothing will be found.

ffffuturexu avatar Jan 13 '25 11:01 ffffuturexu

Hi @ffffuturexu,

there are indeed multiple tickers for the same ISIN. The only way I see to retrieve this info is to parse HTML page (e.g.).

What I noticed though is that for different countries the ticker is changing, e.g. justetf_scraping.load_overview(isin="IE00BM8R0J59", local_country="IT"). Oddly the ticker isn't changing with a particular exchange/currency. Would it help you or you rather need the whole overview over all possible combinations for each of ISINs you are interested in?

FYI, I get the following results for different countries:

Country Ticker
DE QYLE
AT QYLE
CH QYLD
GB QYLP
IT QYLD
FR QYLE
ES QYLE
NL QYLD
BE -

druzsan avatar Jan 18 '25 15:01 druzsan

Hi @druzsan

Thanks for the solution. This solution can help me with the specific mapping request. I think I can manually map them by iterating the country codes. Therefore, the mapping of "isin"-"country"-"ticker" works.

While the ticker seems to only link to the country rather than the exchange and the currency, which means the currency I want cannot be confirmed for my interested ETF when I run load_chart(). For the same example, QYLD and QYLP are both listed in the London exchange but with two different currencies, and QYLD is also listed in the Italian exchange.

So, the "isin"-"country"-"ticker"-"exchange"-"currency" might be a comprehensive mapping. Maybe the only way is to parse the HTML as you mentioned. (this is not a request BTW, just for discussion :-)

ffffuturexu avatar Jan 29 '25 14:01 ffffuturexu

Hi @ffffuturexu,

from my experience the country is the only parameter that changes ticker. Unfortunately, changing exchange and/or currency didn't really changed the ticker for me.

BTW, did you double-check it outside of justETF? Are there really two different tickers for the London exchange with different currencies but the same ISIN? Sounds strange to me, but I have only a limited overview over this.

In this case parsing the HTML would be indeed the only option.

druzsan avatar Feb 02 '25 08:02 druzsan

Thanks@druzsan

First, I have developed my own function to query information by ticker using requests, by referring to your source code in overview.py. This ultimately solved my initial problem.

Regarding the ISIN-ticker-currency-exchange mapping, I attached two screenshots from trading212, showing the complicated situation of mapping.

ffffuturexu avatar Feb 03 '25 14:02 ffffuturexu

Hi @ffffuturexu,

little late, but anyway.

First, I have developed my own function to query information by ticker using requests, by referring to your source code in overview.py. This ultimately solved my initial problem.

Can you post it here? Or just tell, which query key do you use? I tried ...&ticker=QYLE, but it just gives me the whole overview table.

druzsan avatar Mar 23 '25 21:03 druzsan

Hi

The basic idea is to query the ISIN using the ticker firstly. Once the unique identifier ISIN is acquired, then other queries can be done.

1. Get the ISIN using the ticker

I create a function: def ticker_to_isin(ticker: str, locale: str) -> str: within which I did a post query that is similar to the function def get_raw_overview

with requests.Session() as session:
    # 1. Load the initial page to set cookies and parse the dynamic counter.
    session.headers["User-Agent"] = USER_AGENT
    initial_url = f"{BASE_URL}?search=ETFS"
    html_response = session.get(initial_url)
    if html_response.status_code != 200:
        raise Exception(f"Failed to load initial page: status code {html_response.status_code}")
    
    if match := PATTERN.search(html_response.text):
        counter = match.group(1)
        # print(f"Found dynamic counter: {counter}")
    else:
        warnings.warn("Cannot parse dynamic counter from HTML page, assuming 0.")
        counter = "0"
    
    # 2. Construct the AJAX (Wicket) URL using the dynamic counter and including the ticker.
    ajax_url = (
        f"{BASE_URL}?{counter}-1.0-container-tabsContentContainer-tabsContentRepeater-1-container-content-etfsTablePanel"
        f"&query={ticker}&search=ETFS&_wicket=1"
    )
    
    # 3. Prepare POST data.
    # Here we include minimal parameters; the ticker can be passed in the POST payload as well if needed.
    data = {
        **BASE_DATA,
        "lang": "en",
        "country": {locale},
        "universeType": "private",
        "defaultCurrency": "EUR",
        # Including etfsParams here is optional since the ticker is in the URL,
        # but you can add more filters if required.
        "etfsParams": f"search=ETFS&query={ticker}",
    }
    
    # 4. Send the POST request.
    response = session.post(ajax_url, data=data)
    if response.status_code != 200:
        raise Exception(f"Failed to fetch ETF data: status code {response.status_code}")
    
    try:
        json_data = response.json()
        # print(json_data)
    except json.JSONDecodeError as e:
        raise Exception("Failed to parse JSON response") from e

    # Debug print: show all tickers in the response
    # for etf in json_data.get("data", []):
    #     print("Record:", etf)
    
    # 5. Search through the returned data for the row matching the ticker.
    # The JSON structure is assumed to have a key 'data' which is a list of ETF records.
    etfs = json_data.get("data", [])
    for etf in etfs:
        isin = etf.get("isin")
        if isin:
            return isin
        else:
            raise Exception(f"ETF found for ticker {ticker} but no ISIN present")
    
    raise Exception(f"ETF with ticker '{ticker}' not found in the response")

2. Get the quotes using the ISIN (or your load_charts, etc.)

After I get the ISIN, I use an API to get the latest quotes BASE_QUOTE_URL = 'https://www.justetf.com/api/etfs/{isin}/quote?locale={locale}&currency={currency}'

For example the QYLD (IE00BM8R0J59) https://www.justetf.com/api/etfs/IE00BM8R0J59/quote?locale=IT&currency=EUR

The locale could be DE, IT, FR, etc...

This will return a json to me

ffffuturexu avatar Mar 24 '25 22:03 ffffuturexu