import numpy as np import pandas as pd import pytest from quantcore.data.download_service import splice_adjusted from quantcore.data.fx import FXConverter from quantcore.data.yahoo import apply_price_scale, normalise_info from quantcore.universe.sources import parse_ishares_holdings from quantcore.universe.symbols import normalise_share_class_name, to_yahoo_symbol def test_fx_conversion_and_log_return_identity(): idx = pd.bdate_range("2024-01-01", periods=5) fx = pd.DataFrame({"USD": [0.85, 0.86, 0.84, 0.85, 0.87], "CHF": 1.0}, index=idx) conv = FXConverter(fx, base="CHF") px = pd.DataFrame({"A": [10.0, 11.0, 10.5, 10.8, 11.2], "B": [100.0, 101.0, 99.0, 98.0, 100.0]}, index=idx) cur = pd.Series({"A": "USD", "B": "CHF"}) chf = conv.convert_prices(px, cur) np.testing.assert_allclose(chf["A"], px["A"] * fx["USD"]) np.testing.assert_allclose(chf["B"], px["B"]) # CHF log-return = local log-return + FX log-return r_chf = np.log(chf).diff().iloc[1:] r_loc = np.log(px).diff().iloc[1:] r_fx = conv.fx_log_returns(cur, idx).iloc[1:] np.testing.assert_allclose(r_chf.values, (r_loc + r_fx).values) with pytest.raises(KeyError): conv.convert_prices(px, pd.Series({"A": "JPY", "B": "CHF"})) def test_gbp_pence_normalisation(): info = normalise_info("HSBA.L", {"currency": "GBp", "marketCap": 1e11, "regularMarketPrice": 1533.8, "firstTradeDateMilliseconds": 584089200000, "quoteType": "EQUITY"}) assert info["currency"] == "GBP" and info["price_scale"] == 100.0 assert info["last_price"] == pytest.approx(15.338) assert info["first_trade_date"] == "1988-07-05" px = pd.DataFrame({"HSBA.L": [1500.0, 1600.0], "NESN.SW": [80.0, 81.0]}) scaled = apply_price_scale(px, pd.Series({"HSBA.L": 100.0})) assert scaled["HSBA.L"].iloc[0] == 15.0 and scaled["NESN.SW"].iloc[1] == 81.0 def test_splice_rescales_history_on_dividend(): idx = pd.bdate_range("2024-01-01", periods=10) stored = pd.DataFrame({"A": np.linspace(100, 109, 10), "B": np.linspace(50, 59, 10)}, index=idx) fresh_idx = pd.bdate_range("2024-01-08", periods=7) # overlaps the last 5 stored days, adds 2 new fresh = pd.DataFrame({"A": np.concatenate([stored["A"].iloc[5:].values * 0.98, [110 * 0.98, 111 * 0.98]]), "B": np.concatenate([stored["B"].iloc[5:].values, [60.0, 61.0]])}, index=fresh_idx) out, rescaled = splice_adjusted(stored, fresh) assert rescaled == ["A"] assert len(out) == 12 np.testing.assert_allclose(out["A"].iloc[:5], stored["A"].iloc[:5] * 0.98) # history rescaled np.testing.assert_allclose(out["A"].iloc[5:], fresh["A"]) # fresh window authoritative np.testing.assert_allclose(out["B"], np.concatenate([stored["B"].values, [60.0, 61.0]])) def test_symbol_mapping_rules(): assert to_yahoo_symbol("NOVO B", "Omx Nordic Exchange Copenhagen A/S") == "NOVO-B.CO" assert to_yahoo_symbol("BT.A", "London Stock Exchange") == "BT-A.L" assert to_yahoo_symbol("RR.", "London Stock Exchange") == "RR.L" assert to_yahoo_symbol("NESN", "SIX Swiss Exchange") == "NESN.SW" assert to_yahoo_symbol("BRK.B", country="United States") == "BRK-B" assert to_yahoo_symbol("NDA FI", "Nasdaq Omx Helsinki Ltd.") == "NDA-FI.HE" with pytest.raises(ValueError): to_yahoo_symbol("X", "Mars Exchange") assert normalise_share_class_name("Alphabet Inc. Class A") == normalise_share_class_name("Alphabet Inc. Class C") assert normalise_share_class_name("Investor AB ser. B") == normalise_share_class_name("Investor Class A") def test_parse_ishares_holdings(): text = ('Fondsposition per,"18.Sept.2026"\n \nEmittententicker,Name,Sektor,Anlageklasse,Marktwert,' 'Gewichtung (%),Nominalwert,Nominale,Kurs,Standort,Börse,Marktwährung\n' '"ASML","ASML HOLDING","IT","Aktien","438.829.188,00","4,23","438.829.188,00","303.478,00","1.446,00",' '"Niederlande","Euronext Amsterdam","EUR"\n' '"NOVO B","NOVO NORDISK CLASS B","Gesundheitsversorgung","Aktien","1,00","1,00","1,00","1,00","1,00",' '"Dänemark","Omx Nordic Exchange Copenhagen A/S","DKK"\n' '"XEUR","EUR CASH","Barmittel und/oder Derivate","Geldmarkt","1,00","0,10","1,00","1,00","1,00",' '"Deutschland","-","EUR"\n') df = parse_ishares_holdings(text) assert list(df["symbol"]) == ["ASML.AS", "NOVO-B.CO"] assert df.iloc[0]["gics_sector"] == "Information Technology" and df.iloc[0]["country"] == "Netherlands" assert df.iloc[0]["index_weight"] == pytest.approx(4.23) assert df.iloc[0]["source_date"] == "2026-09-18"