본문 바로가기
C.W.K.
Stream
Lesson 04 of 05 · published

회사 하나, 상장 둘, 행 하나

~12 min · identity, deduplication, domain-modeling, keys

Level 0원석
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
모집단은 회사의 집합이다. 데이터는 상장의 집합으로 온다. 서로 다른 집합이고, 그 차이는 겉모양이 아니다.

버그, 사실은 모델링 오류

집중도는 제일 큰 열 개 회사가 시장의 얼마를 차지하냐를 물어. 입력은 상장이야. 거래되는 증권마다 행 하나. 그리고 어떤 회사는 상장이 하나가 아니야. 주식 종류가 둘인 회사는 종목 코드 둘로 거래돼.

여기 물리게 만드는 사실을 더해봐. 제공자가 돌려주는 주식 수가 종류 조정된 값이야. 상장마다 가중 주식 수가 이미 회사 전체를 계산에 넣고 있어. 그래서 이중 종류 회사의 종목 둘이 각각 독립적으로 회사 전체를 값 매기고, 둘 다 두면 한 회사가 상위 열 개에 두 번 들어가. 두 번 다 전체 크기로.

작은 왜곡이 아니야. 분자를 부풀리고, 분모를 잘못 말하고, 진짜 10 위 회사를 목록 밖으로 밀어내. 그것도 좁은 의미로는 완전히 맞는 집합에서. 모든 행이 진짜였어. 모든 값이 맞았고. 그 집합이 그냥 물어본 것과 다른 질문에 답하고 있었을 뿐이야.

키를 고르기 전에 네 질문의 단위 이름을 대. "제일 큰 열 개 회사" 는 회사에 대한 질문이야. 데이터가 상장이나 계정이나 기기나 세션이나 거래로 키가 잡혀 있으면, 질문의 단위로 먼저 접어야 해. 그리고 그 접기에는 편한 정체성이 아니라 진짜 정체성이 필요하고.

정체성 고르기

종목 코드는 있고, 유일하고, 틀렸어. 상장을 식별하니까. 회사 이름은 소스랑 표기에 따라 달라지는 문자열이고. 맞는 키는 규제 공시 식별자야. 공시 주체마다 발급되는 번호고, 이 도메인에서 "회사" 가 갖는 정체성이 정확히 그거거든.

구현에서 훔쳐갈 만한 게 둘 있어.

폴백이 공유가 아니라 행별이야. 공시 식별자가 없는 상장은 빈 문자열이 아니라 자기 종목 코드로 폴백해. 보이는 것보다 중요해. 빈 문자열 폴백이었으면 식별자 없는 상장 전부가 유령 회사 하나로 접혔을 거고, 그건 지금 고치는 버그보다 훨씬 나쁜 버그야.

접을 때 최댓값을 잡지 절대 합을 안 잡아. 상장마다 이미 회사 전체를 값 매기니까 더하면 두 배가 돼. 그래서 상장 하나가 대표가 되고 나머지는 버려져. 코드에 그거에 대한 주석이 달려 있어. 다음에 읽는 사람이 왜 합을 안 잡는지 반드시 궁금해할 테니까.

근데 그 주석은 천천히 읽어. 이 레슨을 처음 쓸 때, 주석이랑 코드가 안 맞았거든. 주석은 더 유동성 높은 상장을 남긴다고 했고, 코드는 계산된 시총이 더 큰 쪽을 남겼어. 거래대금은 애초에 어떤 상장이 후보 풀에 들어오냐만 정했지 여기서는 아무 역할도 안 했고. 이중 종류 회사면 보통 둘이 일치해서, 그래서 이 불일치가 3 주를 살아남았어. 대부분의 경우 맞는 주석이 틀렸다는 걸 알아채기 제일 어려워.

2026-08-31 에 주석 쪽을 고쳤어. 코드가 아니라. 어느 쪽을 고치느냐가 이 레슨에서 제일 값나가는 판단이야.

코드가 맞았거든. 이중 종류 회사에서 대표를 고를 때 시총이 큰 쪽을 잡는 건 타당해. 어차피 상장마다 회사 전체를 값 매기니까 둘 중 뭘 잡아도 같은 회사고, 큰 쪽이 보통 유동성도 크고, 무엇보다 이미 들고 있는 숫자 야. 주석대로 진짜 유동성 비교로 바꾸려면 거래대금을 이 단계까지 끌고 내려와야 하는데, 그건 안 물어본 동작 변경이고 순위를 실제로 움직여. 라벨이 코드보다 앞서갔을 때 기본값은 라벨을 내리는 거지 코드를 라벨까지 올리는 게 아니야. 반대로 하면 문서를 근거로 동작을 바꾸는 셈인데, 그 문서는 방금 틀렸다고 판명난 그거잖아.

주석이 코드랑 갈렸으면, 먼저 어느 쪽이 진실인지부터 정해. "불일치를 고쳤다" 는 아직 아무 말도 안 한 거야. 주석을 코드에 맞추면 문서 수정이고 위험이 0 이야. 코드를 주석에 맞추면 동작 변경이고, 그 변경의 근거는 방금 신뢰를 잃은 문장 하나야. 둘이 같은 크기의 일처럼 보이지만 전혀 아니고, 급할 때 사람들이 자주 두 번째를 골라. 주석이 의도 처럼 읽히거든.
두 오류 다 조용하고 방향이 반대야. 이중 종류 시총을 더하면 회사가 부풀어. 빈 키로 접으면 상관없는 회사들이 하나로 합쳐지고. 둘 다 예외를 안 던지고, 둘 다 타입 검사를 통과하고, 둘 다 완전히 정상으로 보이는 순위를 내놔. 이 트랙 전체의 반복되는 주제지.

Code

상장을 회사로 접기, 함정 둘 다 처리해서·python
# One row per COMPANY, not per listing. `weighted_shares_outstanding`
# is class-adjusted, so BOTH listings of a dual-class company each
# price the whole firm -- keeping both put one company in the top ten
# twice. Deduped on the filing identifier, the same identity the
# filing backfill follows; a listing with no identifier falls back to
# its OWN symbol rather than collapsing into a shared empty key.
by_company: dict[str, dict[str, Any]] = {}
for bar in candidates:
    held = cache.get(bar["symbol"])
    if not held:
        continue
    entry = {
        "symbol": bar["symbol"], "name": held["name"],
        "cik": held["cik"] or "",
        "market_cap": bar["close"] * held["shares"],
        "price": bar["close"],
    }
    company = entry["cik"] or f"sym:{entry['symbol']}"
    prior = by_company.get(company)
    # Same company, two listings: keep ONE as the face, and NEVER
    # add their caps together. Note what the tiebreak actually is
    # -- the larger computed market_cap, not the more liquid
    # listing. Turnover only decided who got INTO `candidates`.
    # (The real module's comment said "more liquid" here until
    #  2026-08-31 while this same line compared caps. The
    #  comment was brought down to the code, not the other
    #  way. Read the line, not the label.)
    if prior is None or entry["market_cap"] > prior["market_cap"]:
        by_company[company] = entry

ranked = sorted(by_company.values(),
                key=lambda r: r["market_cap"], reverse=True)

External links

Exercise

네가 만드는 순위나 집계를 하나 골라서, 그 질문이 다루는 단위랑 행이 키로 잡힌 단위를 적어봐. 다르면 접기 단계를 찾아. 두 가지를 확인해. 정체성이 없을 때 폴백이 행별인지 공유인지, 그리고 접을 때 합을 잡는지 극값을 잡는지. 둘 다 거꾸로 하기 쉽고 둘 다 예외를 안 던져.
Hint
공유 폴백이 둘 중 더 위험해. 데이터 품질 문제에 비례해서 커지거든. 식별자 없는 행이 많을수록 유령 개체가 커지고, 그건 항상 버그가 아니라 말도 안 되게 큰 구성원 하나처럼 보여.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고
💛 by 피파— warm

댓글 0

🔔 답글 알림 (로그인 필요)
로그인 — 댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.