"""Unit tests — contract symbol parsing and root aliases.""" from __future__ import annotations import pytest from core.errors import ApiError from futures.symbols import ( ALIASES_OF_ROOT, ROOT_ALIASES, canonical_symbol, expand_year, normalize_root, parse_symbol, symbol_from_file_stem, ) @pytest.mark.parametrize("raw, root, mc, year", [ ("ESZ25", "ES", "Z", 2025), ("ESZ2025", "ES", "Z", 2025), ("ES_Z25", "ES", "Z", 2025), ("ES-Z25", "ES", "Z", 2025), ("esz25", "ES", "Z", 2025), (" esz2025 ", "ES", "Z", 2025), ("CLF26", "CL", "F", 2026), ("M2KH25", "M2K", "H", 2025), ("FDAXZ2024", "FDAX", "Z", 2024), ("SR3Z24", "SR3", "Z", 2024), ("ZKZ25", "ZK", "Z", 2025), # root ending with a month letter ("HHZ25", "HH", "Z", 2025), ("CZ25", "C", "Z", 2025), # 1-char root (London cocoa) ("USZ24", "US", "Z", 2024), ("ESZ99", "ES", "Z", 1999), ("ESZ79", "ES", "Z", 2079), ("ESZ80", "ES", "Z", 1980), ]) def test_parse_symbol(raw, root, mc, year): cs = parse_symbol(raw) assert (cs.root, cs.month_code, cs.year) == (root, mc, year) assert cs.short == f"{root}{mc}{year % 100:02d}" assert cs.long == f"{root}{mc}{year}" assert cs.file_stem == f"{root}_{mc}{year % 100:02d}" @pytest.mark.parametrize("raw, root", [ ("6EZ25", "E6"), ("6JZ25", "J1"), ("6BZ25", "B6"), ("6AZ25", "A6"), ("6CZ25", "AD"), ("6SZ25", "E1"), ("6NZ25", "N6"), ("6MZ25", "MP"), ("6LZ25", "BR"), ("6ZZ25", "T6"), ("ZBZ25", "US"), ("E6Z25", "E6"), ]) def test_aliases_resolve_to_lake_roots(raw, root): assert parse_symbol(raw).root == root assert canonical_symbol(raw) == f"{root}Z25" def test_alias_tables_are_consistent(): for alias, root in ROOT_ALIASES.items(): assert alias in ALIASES_OF_ROOT[root] assert normalize_root(alias) == root assert normalize_root(alias.lower()) == root assert normalize_root("es") == "ES" assert "6E" in ALIASES_OF_ROOT["E6"] and "ZB" in ALIASES_OF_ROOT["US"] @pytest.mark.parametrize("bad", ["", "ES", "ESA25", "ESZ5", "ESZ2025X", "TOOLONGZ25", "ES Z 25", "Z25", "ES25", "ESZ125", "ES_Z_25", "ÉSZ25", "BOGUS", "ESZ1800"]) def test_invalid_symbols_raise_400(bad): with pytest.raises(ApiError) as e: parse_symbol(bad) assert e.value.status == 400 and e.value.code == "INVALID_CONTRACT_SYMBOL" assert e.value.payload()["error"]["docs"].endswith("#invalid_contract_symbol") def test_expand_year(): assert expand_year(0) == 2000 and expand_year(79) == 2079 and expand_year(80) == 1980 and expand_year(99) == 1999 assert expand_year(2031) == 2031 def test_symbol_from_file_stem(): cs = symbol_from_file_stem("ES_Z24_1day") assert cs is not None and cs.short == "ESZ24" and cs.year == 2024 assert symbol_from_file_stem("ES_Z24").short == "ESZ24" assert symbol_from_file_stem("ES_1day") is None assert symbol_from_file_stem("AAPL") is None assert symbol_from_file_stem("ES_ZZ4_1day") is None