Skip to content

Systems

systems

Classification Systems supported by equities_classifier.

GECS = ClassificationSystem(id=ClassificationSystemID.GECS, display_name='Global Equity Classification Structure', short_name='GECS', authorities=('Morningstar',), hierarchy=('Super Sector', 'Sector', 'Industry'), supports_codes=False) module-attribute

GICS = ClassificationSystem(id=ClassificationSystemID.GICS, display_name='Global Industry Classification Standard', short_name='GICS', authorities=('MSCI', 'S&P Dow Jones Indices'), hierarchy=('Sector', 'Industry Group', 'Industry', 'Sub-Industry'), supports_codes=True) module-attribute

GICS_DICT: dict[str, str] = {code: _normalize_gics_name(definition['name']) for code, definition in GICSDefinition('').definition.items()} module-attribute

GICS_INVDICT: dict[tuple[str, int], str] = {(_normalize_gics_name(definition['name']), len(code) // 2): code for code, definition in GICSDefinition('').definition.items()} module-attribute

MAP_GECS_SUPERSECTOR_FROM_SECTOR: immutabledict[str, str] = immutabledict({'Basic Materials': 'Cyclical', 'Consumer Cyclical': 'Cyclical', 'Financial Services': 'Cyclical', 'RealEstate': 'Cyclical', 'Communication Services': 'Sensitive', 'Energy': 'Sensitive', 'Industrials': 'Sensitive', 'Technology': 'Sensitive', 'Healthcare': 'Defensive', 'Consumer Defensive': 'Defensive', 'Utilities': 'Defensive'}) module-attribute

resolve_gecs_supersector(nodes: dict[ClassificationLevel, ClassificationNode]) -> Mapping[ClassificationLevel, ClassificationNode]

Resolve missing GECS super sector.

Source code in src/equities_classifier/classification/systems.py
def resolve_gecs_supersector(
    nodes: dict[ClassificationLevel, ClassificationNode],
) -> Mapping[ClassificationLevel, ClassificationNode]:
    """Resolve missing GECS super sector."""

    result = dict(nodes)

    sector = result.get(ClassificationLevel.LEVEL2)
    if sector is None:
        return result

    super_sector = MAP_GECS_SUPERSECTOR_FROM_SECTOR.get(sector.value,)
    if super_sector is None:
        return result

    result.setdefault(
        ClassificationLevel.LEVEL1,
        ClassificationNode(value=super_sector,),
    )

    return result

resolve_gics_motleyfool(nodes: Mapping[ClassificationLevel, ClassificationNode]) -> Mapping[ClassificationLevel, ClassificationNode]

Resolve and validate Motley Fool GICS classification.

Source code in src/equities_classifier/classification/systems.py
def resolve_gics_motleyfool(
    nodes: Mapping[ClassificationLevel, ClassificationNode],
) -> Mapping[ClassificationLevel, ClassificationNode]:
    """Resolve and validate Motley Fool GICS classification."""

    result = dict(nodes)

    sector_node = result.get(ClassificationLevel.LEVEL1)
    industry_node = result.get(ClassificationLevel.LEVEL3)

    if industry_node is None:
        return result

    # Find GICS industry (level 3) from Motley Fool industry name.
    industry_code: str | None = _find_gics_code(industry_node.value, 3)

    if industry_code is None:
        ClassificationHelper.classification_element_invalid(
            DataSourceID.MOTLEYFOOL,
            ClassificationSystemID.GICS,
            ClassificationLevel.LEVEL3,
            industry_node.value,
        )
        return result

    # GICS level 3 must consist of six digits.
    if len(industry_code) < 6:
        return result

    # Derive the complete hierarchy from the level-3 code.
    sector_code = industry_code[:2]
    industry_group_code = industry_code[:4]

    # Validate Motley-Fool sector against the sector derived
    # from the GICS industry code.
    if sector_node is not None:

        expected_sector = GICS_DICT.get(sector_code)
        if expected_sector is not None:
            actual_sector = _normalize_gics_name(sector_node.value,)
            if actual_sector != expected_sector:
                # Sector and industry disagree. Do not silently
                # create an inconsistent classification.
                ClassificationHelper.classification_mismatch(
                    DataSourceID.MOTLEYFOOL,
                    ClassificationSystemID.GICS,
                    ClassificationLevel.LEVEL1,
                    actual_sector,
                    expected_sector,
                )

    # Replace / enrich level 1 with the canonical GICS value.
    sector_name = GICS_DICT.get(sector_code)
    if sector_name is not None:
        result[ClassificationLevel.LEVEL1] = ClassificationNode(
            # value=sector_name,
            # use uppercase name from GICS definition
            value=GICSDefinition(sector_code).sector.name,
            code=sector_code,
        )

    # Level 2 is not supplied by Motley Fool.
    industry_group_name = GICS_DICT.get(industry_group_code)
    if industry_group_name is not None:
        result[ClassificationLevel.LEVEL2] = ClassificationNode(
            # value=industry_group_name,
            # use uppercase name from GICS definition
            value=GICSDefinition(industry_group_code).industry_group.name,
            code=industry_group_code,
        )

    # Level 3 comes from Motley Fool but gets the canonical GICS code/name.
    industry_name = GICS_DICT.get(industry_code)
    if industry_name is not None:
        result[ClassificationLevel.LEVEL3] = ClassificationNode(
            # value=industry_name,
            # use uppercase name from GICS definition
            value=GICSDefinition(industry_code).industry.name,
            code=industry_code,
        )

    return result