# -*- coding: utf-8 -*- """시즌 등록부. 시즌마다 달라지는 것은 전부 여기 있고 다른 곳에는 없다. ODC와 같은 원칙을 따른다. 시즌별 규칙은 공유하지 않고 시즌마다 되풀이해 적는다. 시즌 2에는 시즌 1에서 발견한 결함의 교정이 들어가는데, 그 교정이 시즌 1에 닿아서는 안 되기 때문이다. 시즌 1 참가자는 이미 공개된 규칙 아래 채점됐다. 시즌 1과 2는 겹쳐 돌아간다. 그래서 '지금 시즌'은 상수가 아니라 조회다. 정규화 계수(운의 한계선)의 근거 수치는 calibrate.py가 산출해 luck_ceiling.json에 넣는다. 개막 전 데이터로만 계산하고 시즌 중에는 재계산하지 않는다. """ SEASONS = { 1: { "name": "시즌 1 · 네 개의 시장", "opens": "2026-08-24", "closes": "2026-12-24", # 성격이 갈리는 네 시장. 서로 거의 무상관이라(-0.04 ~ 0.19) 참가자가 흩어진다. # 도지코인은 비트코인과 상관 0.763, 반도체ETF는 엔비디아와 0.797이라 뺐다. # 두 종목이 사실상 하나가 되면 4종목이 3종목이 된다. # # AI가 만든 것 엔비디아 ↕ 인간이 오래 믿은 것 금 # AI가 흔든 것 비트코인 ↕ 세상을 굴려온 것 유가 # # 🔴 엔비디아 실적이 개막 이틀 뒤(8/26)다. 실적일 변동은 평상일의 2.7배이고 # 87%가 시가 갭이라 장중 대응이 안 된다. 모두에게 같은 조건이지만 # 초반 순위에 운이 크게 섞인다는 점은 참가 안내에 명시한다. "assets": { "NVDA": {"label": "엔비디아", "cat": "stock", "vol": 0.514, "why": "AI 그 자체. 이 시장을 예측하는 것이 이 대회의 상징", "src": "NVDA", "hours": "rth", "anchor": {1: 0.781, 5: 0.772, 21: 0.780}}, "BTC": {"label": "비트코인", "cat": "coin", "vol": 0.392, "why": "AI가 흔든 새 화폐. 24시간 거래·휴장 없음", "src": "BTC-USD", "hours": "24h", "anchor": {1: 0.807, 7: 0.801, 30: 0.757}}, "GOLD": {"label": "금", "cat": "commodity", "vol": 0.212, "why": "인류가 5,000년 믿어온 가치. 방향이 없어 운으로 벌기 가장 어렵다", # 🔴 선물(GC=F)이 아니라 ETF를 쓴다. GC=F는 월말 이상점프가 # 정상의 2.4배로, 롤 시점 가격차가 수익률에 섞여 든다. "src": "GLD", "hours": "rth", "anchor": {1: 0.748, 5: 0.762, 21: 0.744}}, "OIL": {"label": "유가", "cat": "commodity", "vol": 0.369, "why": "세상을 굴려온 산업의 심장. AI는 옛 시장도 예측하는가", # 🔴 CL=F는 3년 수익이 +10%인데 USO는 +89%다. 79%p 괴리는 # 근월물을 이어붙일 때 생기는 롤 왜곡이다. 참가자가 실제로 # 낼 수 없는 손익이라 ETF를 쓴다. "src": "USO", "hours": "rth", "anchor": {1: 0.782, 5: 0.780, 21: 0.804}}, }, "prize_usd_per_asset": 500, "prize_total_usd": 2000, "prize_rule": "종목별 최종 수익률 1위에게 각 500달러. 총 2,000달러", # 종목 간 수익률을 직접 비교하는 방식은 폐기했다. 네 번 고쳐도 편중이 # 잡히지 않았다(4.7배 -> 99.6배 -> 3.7배 -> 6.0배). 분포 모양이 종목마다 # 근본적으로 달라, 어느 한 통계량을 맞추면 다른 지점이 어긋난다. # 종목별 시상은 그 비교 자체를 없앤다. 통합 순위는 참고 표시로만 둔다. "leverage": 1.0, "min_participation": 0.70, "update": "hourly", "ledger": "s1/", }, 2: { # 예고만 한다. 종목은 시즌 1이 끝난 뒤 확정한다. # 시즌 1에서 어떤 성격의 시장에 참가자가 모이는지 보고 정하는 편이 낫다. "name": "시즌 2 · 예고", "opens": None, "closes": None, "assets": {}, "status": "announced", "teaser": "선물과 원자재, 그리고 외환", # 실측해둔 후보들. 확정 아님. "candidates": { "COPPER": {"label": "구리", "vol": 0.291, "note": "AI 데이터센터 서사는 있으나 데이터로는 약하다. " "반도체 상관 0.28~0.36 < 금 상관 0.40. " "서사로 내세우면 참가자가 상관표로 반박할 수 있다"}, "OIL": {"label": "유가", "vol": 0.405, "note": "롤오버 처리 필요"}, "USDKRW": {"label": "달러/원", "vol": 0.097, "note": "저변동"}, "USDJPY": {"label": "달러/엔", "vol": 0.093, "note": "저변동"}, "DXY": {"label": "달러지수", "vol": 0.064, "note": "변동성이 금의 1/3. 순위가 소수점에서 갈릴 위험"}, }, "prize_usd_per_asset": 500, "leverage": 1.0, "min_participation": 0.70, "update": "hourly", "ledger": "s2/", # 🔴 선물·외환은 롤오버가 있다. 근월물 만기 시 차월물로 갈아타며 가격이 # 점프하는데, 이걸 수익률에 그대로 넣으면 없는 손익이 생긴다. # 연결선물 구성 방식을 확정하기 전에는 시즌 2를 열지 않는다. "rollover": None, }, } # 참고용 — 화면에 '운의 한계선'을 그을 때 쓴다. 순위 계산에는 쓰지 않는다. LUCK_CEILING = { "SOL": 0.8870, "ETH": 0.6227, "BTC": 0.5821, "TSLA": 0.5264, "NVDA": 0.4399, "OIL": 0.3546, "AAPL": 0.2271, "GOLD": 0.1497, } # 리더보드에 참가자와 함께 놓는 기준선. ODC가 승인약을 같은 표에 넣는 것과 같다. # 척도가 없는 리더보드는 순위를 읽을 수 없다. # # 🔴 주최측 모델은 넣지 않는다. 셋 다 공개된 교과서 기법이다. # 주최자가 자기 모델을 참가자 표에 올리면 "누가 더 나은 모델을 만드는가"라는 # 명분이 흐려진다. 기준선은 척도이지 경쟁자가 아니다. ANCHORS = { "BUYHOLD": "단순 보유 — 포지션 항상 +1. 이걸 못 이기면 의미가 없다", "VOLTARGET": "변동성 타겟팅 — 지수가중 변동성으로 크기만 조절. 방향은 롱 고정", "RANDOM": "무작위 — 매일 -1/0/+1 임의 선택. 바닥", } def season_of(asset): for sid, s in SEASONS.items(): if asset in s.get("assets", {}): return sid return None # 백분위 채점 기준표 — calibrate.py 산출. 종목별 '실력0 참가자' 수익률 분포. _REF = None def _ref(): global _REF if _REF is None: import json, os _REF = json.load(open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "reference_dist.json"), encoding="utf-8")) return _REF def percentile(ret, asset): """그 종목의 실력0 분포에서 내 수익률이 어느 백분위인가.""" import bisect R = _ref() tbl = R["ref"].get(asset) if not tbl: return None return bisect.bisect_left(tbl, ret) / len(tbl) def score(ret, asset): """순위 점수 = -log10(1 - 백분위) = '운으로 이 성적이 나올 확률'의 역수 지수. 2.0 이면 운으로 이럴 확률 1/100, 3.0 이면 1/1,000. 🔴 수익률을 종목별 계수로 나누는 방식(= 95분위 비율)은 쓰지 않는다. 그 지점만 맞고 꼬리는 안 맞아, 우승 확률이 SOL 22.3% vs GOLD 4.8%로 4.7배 편중됐다. 우승자는 최댓값이고 최댓값은 꼬리 모양이 정한다. 분포 전체를 맞추는 백분위로 바꾸니 편중이 1.6배(노이즈 수준)로 내려갔다. 화면에는 수익률을 그대로 보여준다. 순위만 이 점수로 매긴다. """ import math p = percentile(ret, asset) if p is None: return None return -math.log10(max(1.0 - min(p, 1 - 1e-6), 1e-6)) # ── 참가 유형 ─────────────────────────────────────────────────── # 자기 신고다. 검증할 방법이 없다. 그래서 상금은 통합 순위에만 걸고 # 유형별 순위는 명예로만 둔다. 속이는 사람이 나와도 대회가 무너지지 않게. # 등록을 까다롭게 만들면 사람이 아예 오지 않는데, 그게 더 큰 손실이다. TYPES = { "human": {"badge": "🧑", "label": "사람", "desc": "직접 판단해 제출"}, "expert": {"badge": "📊", "label": "전문가·기관", "desc": "자체 알고리즘"}, "agent": {"badge": "🤖", "label": "AI 에이전트", "desc": "자동 제출"}, } # 유형별 참여 요건. 사람은 매시간 갱신할 수 없으니 접속 빈도로 완화한다. # 포지션을 그대로 두는 것도 전략이다 — NVDA에서 단순보유가 변동성 타겟팅을 이겼다. PARTICIPATION = { "human": {"valid_ratio": 0.70, "min_visits_per_week": 1}, "expert": {"valid_ratio": 0.70, "min_visits_per_week": 0}, "agent": {"valid_ratio": 0.70, "min_visits_per_week": 0}, } # 상금은 통합 1명. 유형별 1위는 명예 부문이다. PRIZE_SCOPE = "overall" HONOR_SCOPES = ["human", "expert", "agent"] MANIFESTO = { "title": "AI는 시장을 이길 수 있는가", "sub": "122일간의 공개 실험", "cta_agent": "당신의 에이전트를 링 위에 올리십시오", "cta_human": "직접 참가하기", "why": [ ("성공만 보도됩니다", "수익 낸 사람만 인터뷰합니다. 진 사람 999명은 기록도 남지 않습니다."), ("백테스트는 조작됩니다", "과거 데이터로 연 200%는 누구나 만듭니다. 미래로는 안 됩니다."), ("운과 실력이 안 나뉩니다", "한 달에 30% 낸 사람이 실력인지 운인지 판별할 방법이 없었습니다."), ], "how": [ ("운의 크기를 숫자로 압니다", "실력 0인 참가자가 122일에 어디까지 가는지 20,000회 시뮬레이션으로 쟀습니다. " "수수료까지 물린 값으로 비트코인 +86.6%, 금 +9.2%. " "이 선을 넘지 못한 수익은 실력의 증거가 되지 않습니다."), ("조작이 불가능합니다", "정답이 미래에 있습니다. 제출한 뒤에 세상이 정답을 만듭니다."), ("같은 조건입니다", "레버리지 1 고정, 같은 데이터, 같은 시각. 크게 걸어 이기는 길을 막았습니다."), ], # 주최측은 순위에 들어가지 않는다. 대신 기준선 셋을 같은 표에 올려 # 참가자가 자기 성적을 무엇에 견줘야 하는지 볼 수 있게 한다. "our_stake": "주최측은 순위에 참가하지 않습니다. 대신 단순 보유·변동성 타겟팅·무작위 " "세 기준선을 같은 표에 올려, 성적을 무엇에 견줄지 보이게 합니다.", }