SeaWolf-AI commited on
Commit
cab04ab
ยท
verified ยท
1 Parent(s): 0d0f436

show received entries before scoring begins

Browse files
Files changed (6) hide show
  1. app/fetch.py +3 -4
  2. app/i18n.py +247 -254
  3. app/main.py +24 -3
  4. app/static/index.html +23 -0
  5. reference.py +124 -124
  6. scoring.py +6 -7
app/fetch.py CHANGED
@@ -1,10 +1,9 @@
1
  # -*- coding: utf-8 -*-
2
  """์‹œ์„ธ ์ˆ˜์ง‘ โ€” ์†Œ์Šค ํ•˜๋‚˜์— ๊ธฐ๋Œ€์ง€ ์•Š๋Š”๋‹ค.
3
 
4
- ๐Ÿ”ด HF Space์—์„œ yfinance ๊ฐ€ **๋นˆ ๋ฐ์ดํ„ฐ**๋ฅผ ๋Œ๋ ค์ค€๋‹ค. ์˜ˆ์™ธ๋„ ์•„๋‹ˆ๊ณ  0๊ฑด์ด๋ผ
5
- ์กฐ์šฉํžˆ ์‹คํŒจํ•œ๋‹ค. Yahoo๊ฐ€ ๋ฐ์ดํ„ฐ์„ผํ„ฐ IP๋ฅผ ๋ง‰๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
6
- ORACLE ๋•Œ ๋ฐ”์ด๋‚ธ์Šค๊ฐ€ ๊ฐ™์€ ์ด์œ ๋กœ ๋ง‰ํ˜”๊ณ , ๊ทธ๋•Œ ์žฌ์‹œ๋„ 3ํšŒ์— 5.2์ดˆ๋ฅผ ๋ฒ„๋ ธ๋‹ค.
7
- ์ด๋ฒˆ์—” ์ฒ˜์Œ๋ถ€ํ„ฐ ์‚ฌ๋‹ค๋ฆฌ๋กœ ๋งŒ๋“ ๋‹ค.
8
 
9
  ์ˆœ์„œ
10
  1) Yahoo chart API ์ง์ ‘ ํ˜ธ์ถœ โ€” yfinance ์˜ ์ฟ ํ‚ค/ํฌ๋Ÿผ ์ ˆ์ฐจ๋ฅผ ๊ฑด๋„ˆ๋›ด๋‹ค
 
1
  # -*- coding: utf-8 -*-
2
  """์‹œ์„ธ ์ˆ˜์ง‘ โ€” ์†Œ์Šค ํ•˜๋‚˜์— ๊ธฐ๋Œ€์ง€ ์•Š๋Š”๋‹ค.
3
 
4
+ ๐Ÿ”ด ์‹œ์„ธ ์ œ๊ณต์ž๋“ค์€ ๋ฐ์ดํ„ฐ์„ผํ„ฐ IP๋ฅผ ๋ง‰๋Š”๋‹ค. ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋นˆ ์‘๋‹ต์ด๋‚˜
5
+ ๋ด‡ ์ฐจ๋‹จ ํŽ˜์ด์ง€๋ฅผ 200์œผ๋กœ ๋Œ๋ ค์ฃผ๋ฏ€๋กœ, ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋งŒ์œผ๋กœ๋Š” ์žกํžˆ์ง€ ์•Š๋Š”๋‹ค.
6
+ ๊ทธ๋ž˜์„œ ์†Œ์Šค๋ฅผ ํ•˜๋‚˜ ๋‘์ง€ ์•Š๊ณ  ์‚ฌ๋‹ค๋ฆฌ๋กœ ๋‚ด๋ ค๊ฐ„๋‹ค.
 
7
 
8
  ์ˆœ์„œ
9
  1) Yahoo chart API ์ง์ ‘ ํ˜ธ์ถœ โ€” yfinance ์˜ ์ฟ ํ‚ค/ํฌ๋Ÿผ ์ ˆ์ฐจ๋ฅผ ๊ฑด๋„ˆ๋›ด๋‹ค
app/i18n.py CHANGED
@@ -1,254 +1,247 @@
1
- # -*- coding: utf-8 -*-
2
- """์ด์ค‘์–ธ์–ด ๋ฌธ๊ตฌ โ€” ์„œ๋ฒ„๊ฐ€ ๋‚ด๋ ค์ฃผ๋Š” ๋ชจ๋“  ์‚ฌ๋žŒ์šฉ ๋ฌธ์žฅ์ด ์—ฌ๊ธฐ ์žˆ๋‹ค.
3
-
4
- ๐Ÿ”ด ํ™”๋ฉด ๋ฌธ๊ตฌ๋งŒ ๋ฒˆ์—ญํ•˜๋ฉด ๋ฐ˜์ชฝ์ด๋‹ค. ์ทจ์ง€ยท๊ทœ์น™ยท์ฑ„์  ํžŒํŠธยท๊ฒฝ๊ณ ๋Š” ์„œ๋ฒ„๊ฐ€ ๋งŒ๋“ค์–ด
5
- ๋‚ด๋ ค๋ณด๋‚ด๋ฏ€๋กœ, ์ด์ชฝ์„ ์•ˆ ํ•˜๋ฉด ์˜์–ด ํ™”๋ฉด์— ํ•œ๊ตญ์–ด๊ฐ€ ์„ž์ธ๋‹ค.
6
-
7
- ๐Ÿ”ด MCP ๋„๊ตฌ ์„ค๋ช…๋„ ์–ธ์–ด๋ฅผ ํƒ„๋‹ค. ์—์ด์ „ํŠธ๋Š” ๊ทธ ์„ค๋ช…์„ ์ฝ๊ณ  ํŒ๋‹จํ•˜๋ฏ€๋กœ
8
- ์˜์–ด๊ถŒ ์—์ด์ „ํŠธ์—๊ฒŒ๋Š” ์˜์–ด๋กœ ์ค˜์•ผ ํ•œ๋‹ค.
9
-
10
- ํ‚ค๊ฐ€ ์—†์œผ๋ฉด ํ•œ๊ตญ์–ด๋กœ ๋–จ์–ด์ง„๋‹ค. ์กฐ์šฉํžˆ ๋นˆ์นธ์ด ๋˜๋Š” ๊ฒƒ๋ณด๋‹ค ๋‚ซ๋‹ค.
11
- """
12
-
13
- DEFAULT = "ko"
14
- LANGS = ("ko", "en")
15
-
16
-
17
- def pick(accept_language, explicit=None):
18
- """์–ธ์–ด ๊ฒฐ์ •. ๋ช…์‹œ ์„ ํƒ์ด ์žˆ์œผ๋ฉด ๊ทธ๊ฒŒ ์ด๊ธด๋‹ค.
19
-
20
- Accept-Language ๋Š” "en-US,en;q=0.9,ko;q=0.8" ๊ฐ™์€ ๋ชจ์–‘์ด๋‹ค.
21
- ์•ž์—์„œ๋ถ€ํ„ฐ ์šฐ๋ฆฌ๊ฐ€ ์•„๋Š” ์–ธ์–ด๊ฐ€ ๋‚˜์˜ค๋Š” ์ฒซ ํ•ญ๋ชฉ์„ ์“ด๋‹ค.
22
- """
23
- if explicit in LANGS:
24
- return explicit
25
- for part in (accept_language or "").split(","):
26
- code = part.split(";")[0].strip().lower()[:2]
27
- if code in LANGS:
28
- return code
29
- return DEFAULT
30
-
31
-
32
- def t(d, lang):
33
- """{"ko": ..., "en": ...} ์—์„œ ํ•˜๋‚˜ ๊ณ ๋ฅธ๋‹ค. ์—†์œผ๋ฉด ํ•œ๊ตญ์–ด."""
34
- if not isinstance(d, dict):
35
- return d
36
- return d.get(lang) or d.get(DEFAULT)
37
-
38
-
39
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ์ข…๋ชฉ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
40
- ASSET = {
41
- "NVDA": {
42
- "label": {"ko": "์—”๋น„๋””์•„", "en": "NVIDIA"},
43
- "why": {"ko": "AI ๊ทธ ์ž์ฒด. ์ด ์‹œ์žฅ์„ ์˜ˆ์ธกํ•˜๋Š” ๊ฒƒ์ด ์ด ๋Œ€ํšŒ์˜ ์ƒ์ง•",
44
- "en": "AI itself. Predicting this market is what this contest is about."},
45
- },
46
- "BTC": {
47
- "label": {"ko": "๋น„ํŠธ์ฝ”์ธ", "en": "Bitcoin"},
48
- "why": {"ko": "AI๊ฐ€ ํ”๋“  ์ƒˆ ํ™”ํ. 24์‹œ๊ฐ„ ๊ฑฐ๋ž˜ยทํœด์žฅ ์—†์Œ",
49
- "en": "The new money AI shook. Trades 24/7, never closes."},
50
- },
51
- "GOLD": {
52
- "label": {"ko": "๊ธˆ", "en": "Gold"},
53
- "why": {"ko": "์ธ๋ฅ˜๊ฐ€ 5,000๋…„ ๋ฏฟ์–ด์˜จ ๊ฐ€์น˜. ๋ฐฉํ–ฅ์ด ์—†์–ด ์šด์œผ๋กœ ๋ฒŒ๊ธฐ ๊ฐ€์žฅ ์–ด๋ ต๋‹ค",
54
- "en": "What humanity trusted for 5,000 years. Nearly directionless, "
55
- "so luck alone gets you the least here."},
56
- },
57
- "OIL": {
58
- "label": {"ko": "์œ ๊ฐ€", "en": "Crude Oil"},
59
- "why": {"ko": "์„ธ์ƒ์„ ๊ตด๋ ค์˜จ ์‚ฐ์—…์˜ ์‹ฌ์žฅ. AI๋Š” ์˜› ์‹œ์žฅ๋„ ์˜ˆ์ธกํ•˜๋Š”๊ฐ€",
60
- "en": "The industrial heart that moved the world. Can AI read the old markets too?"},
61
- },
62
- }
63
-
64
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ๊ทœ์น™ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
65
- RULES = {
66
- "prize": {"ko": "์ข…๋ชฉ๋ณ„ ์ตœ์ข… ์ˆ˜์ต๋ฅ  1์œ„ %d๋‹ฌ๋Ÿฌ ยท ์ด %d๋‹ฌ๋Ÿฌ",
67
- "en": "$%d to the top final return in each asset ยท $%d total"},
68
- "leverage": {"ko": "ํฌ์ง€์…˜ -1.0 ~ +1.0. ๋ ˆ๋ฒ„๋ฆฌ์ง€ 1 ๊ณ ์ •. ๋ฒ—์–ด๋‚˜๋ฉด ์ž˜๋ฆผ",
69
- "en": "Position -1.0 to +1.0. Leverage fixed at 1. Values outside are clipped."},
70
- "update": {"ko": "๋งค์‹œ๊ฐ„ ๊ฐฑ์‹  ๊ฐ€๋Šฅ. ๋‹ค์‹œ ๋‚ผ ๋•Œ๊นŒ์ง€ ์ง์ „ ํฌ์ง€์…˜์ด ์œ ์ง€๋จ",
71
- "en": "Update hourly if you like. Your last position stands until you replace it."},
72
- "fee_note": {"ko": "ํฌ์ง€์…˜ ๋ณ€๊ฒฝ๋ถ„์—๋งŒ ๋ถ€๊ณผ. ์ž์ฃผ ๋’ค์ง‘์œผ๋ฉด ์ˆ˜์ˆ˜๋ฃŒ๋กœ ์žƒ๋Š”๋‹ค",
73
- "en": "Charged on position changes only. Flip often and the fees eat you."},
74
- "scoring": {"ko": "์ข…๋ชฉ๋ณ„ ์ตœ์ข… ์ˆ˜์ต๋ฅ ๋กœ ์ˆœ์œ„. ์˜†์— '์šด์œผ๋กœ ์ด๋Ÿด ํ™•๋ฅ '์„ ํ•จ๊ป˜ ํ‘œ์‹œ",
75
- "en": "Ranked by final return within each asset, shown next to the odds "
76
- "of getting there by luck."},
77
- "participation": {"ko": "ํ•ด๋‹น ์ข…๋ชฉ ๊ฐœ์„ค~๋งˆ๊ฐ ๊ธฐ๊ฐ„์˜ 70% ์ด์ƒ ํฌ์ง€์…˜์ด ์œ ํšจํ•ด์•ผ ์ƒ๊ธˆ ๋Œ€์ƒ",
78
- "en": "A valid position for at least 70% of the asset's season "
79
- "to be eligible for prizes."},
80
- "luck_note": {"ko": "์‹ค๋ ฅ 0์ธ ์ฐธ๊ฐ€์ž๋ฅผ ์‹œ์ฆŒ ๊ธธ์ด๋งŒํผ 20,000ํšŒ ๋Œ๋ ค ๋‚ธ ์ƒ์œ„ 5% ์ง€์ . "
81
- "์ˆ˜์ˆ˜๋ฃŒ๋ฅผ ๋˜‘๊ฐ™์ด ๋ฌผ๋ ธ์Šต๋‹ˆ๋‹ค. ์ด ์„ ์„ ๋„˜์–ด์•ผ ์‹ค๋ ฅ์˜ ์ฆ๊ฑฐ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.",
82
- "en": "The 95th percentile of 20,000 zero-skill players run over the "
83
- "same season length, charged the same fees. Clear this line "
84
- "before calling a result skill."},
85
- "warning": {"ko": "์—”๋น„๋””์•„ ์‹ค์ ์ด 2026-08-26์ด๋‹ค. ์‹ค์ ์ผ ๋ณ€๋™์€ ํ‰์ƒ์ผ์˜ 2.7๋ฐฐ์ด๊ณ  "
86
- "87%๊ฐ€ ์‹œ๊ฐ€ ๊ฐญ์ด๋ผ ์žฅ์ค‘ ๋Œ€์‘์ด ์•ˆ ๋œ๋‹ค. ์‹œ์ฆŒ ์ค‘ 1ํšŒ๋‹ค.",
87
- "en": "NVIDIA reports on 2026-08-26. Earnings-day moves run 2.7x a normal "
88
- "day and 87% of that lands in the opening gap, so you cannot react "
89
- "intraday. It happens once this season."},
90
- "durability_warn": {"ko": "์›์žฅ์ด ์žฌ์‹œ์ž‘์„ ๊ฒฌ๋”˜๋‹ค๋Š” ์ฆ๊ฑฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. "
91
- "๋ฏธ๋Ÿฌ๋‚˜ ์˜์† ์Šคํ† ๋ฆฌ์ง€๋ฅผ ๋ถ™์ด์‹ญ์‹œ์˜ค.",
92
- "en": "No evidence the ledger survives a restart. "
93
- "Attach a mirror or persistent storage."},
94
- }
95
-
96
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ์ฑ„์  ํžŒํŠธ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
97
- HINT = {
98
- "pending": {"ko": "์ง‘๊ณ„ ์ „", "en": "not scored yet"},
99
- "have_pos": {"ko": "ํฌ์ง€์…˜์€ ์ ‘์ˆ˜๋์Šต๋‹ˆ๋‹ค. ์‹œ์ฆŒ์ด ํ•˜๋ฃจ ์ด์ƒ ์ง„ํ–‰๋˜๋ฉด ์ˆ˜์ต๋ฅ ์ด ๊ณ„์‚ฐ๋ฉ๋‹ˆ๋‹ค.",
100
- "en": "Your position is recorded. Returns appear once the season has "
101
- "run for at least a day."},
102
- "no_pos": {"ko": "์•„์ง ํฌ์ง€์…˜์„ ๋‚ด์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.",
103
- "en": "You have not submitted a position yet."},
104
- "over": {"ko": "์šด์˜ ํ•œ๊ณ„์„ ์„ ๋„˜์—ˆ์Šต๋‹ˆ๋‹ค",
105
- "en": "You are past the luck ceiling"},
106
- "under": {"ko": "์•„์ง ์šด์œผ๋กœ๋„ ๋‚˜์˜ฌ ์ˆ˜ ์žˆ๋Š” ๋ฒ”์œ„์ž…๋‹ˆ๋‹ค",
107
- "en": "Still inside what luck alone could produce"},
108
- "unknown_asset": {"ko": "์•Œ ์ˆ˜ ์—†๋Š” ์ข…๋ชฉ. ๊ฐ€๋Šฅ: %s",
109
- "en": "Unknown asset. Available: %s"},
110
- "need_key": {"ko": "์ฐธ๊ฐ€ ํ‚ค๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํ™”๋ฉด์—์„œ ๋กœ๊ทธ์ธํ•˜๋ฉด fc_ ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ‚ค๊ฐ€ "
111
- "๋‚˜์˜ต๋‹ˆ๋‹ค. MCP ์„ค์ •์˜ Authorization ํ—ค๋”์— ๋„ฃ์–ด ์ฃผ์„ธ์š”.",
112
- "en": "A participant key is required. Sign in on the site to get a key "
113
- "starting with fc_, then put it in the Authorization header of "
114
- "your MCP config."},
115
- "kept": {"ko": "๋‹ค์‹œ ์ œ์ถœํ•  ๋•Œ๊นŒ์ง€ ์ด ํฌ์ง€์…˜์ด ์œ ์ง€๋ฉ๋‹ˆ๋‹ค",
116
- "en": "This position stands until you submit again"},
117
- }
118
-
119
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ์ทจ์ง€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
120
- MANIFESTO = {
121
- "title": {"ko": "AI๋Š” ์‹œ์žฅ์„ ์ด๊ธธ ์ˆ˜ ์žˆ๋Š”๊ฐ€",
122
- "en": "Can AI Beat the Market?"},
123
- "sub": {"ko": "122์ผ๊ฐ„์˜ ๊ณต๊ฐœ ์‹คํ—˜", "en": "A 122-day open experiment"},
124
-
125
- # ๋Œ€ํšŒ์˜ ๋ช…๋ถ„. 3๋ง‰์œผ๋กœ ๊ฐ„๋‹ค.
126
- #
127
- # ๐Ÿ”ด 3๋ง‰์—์„œ "์ง‘๋‹จ์ง€์„ฑ์ด ์‹œ์žฅ์„ ํ‘ผ๋‹ค"๊ณ  ์“ฐ์ง€ ์•Š๋Š”๋‹ค.
128
- # ์šฐ๋ฆฌ ์ž์‹ ์˜ ๋ฐฉํ–ฅ ์˜ˆ์ธก ์—ฐ๊ตฌ๊ฐ€ 60๊ฐœ ๋ณ€์ˆ˜๋กœ ์ „๋ถ€ ์‹คํŒจํ–ˆ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ๋ชป ํ•œ ๊ฒƒ์„
129
- # ์ฐธ๊ฐ€์ž๊ฐ€ ํ•ด๋‚ผ ๊ฒƒ์ด๋ผ๊ณ  ๋ฏธ๋ฆฌ ๋งํ•  ์ˆ˜ ์—†๋‹ค. ๋Œ€์‹  ์ด ๋Œ€ํšŒ๊ฐ€ ์‹ค์ œ๋กœ ๋งŒ๋“ค์–ด๋‚ด๋Š”
130
- # ๊ฒƒ โ€” '๊ฒฝ๊ณ„์˜ ์ธก์ •' โ€” ์„ ๋งํ•œ๋‹ค. ๊ทธ๊ฑด ๊ทผ๊ฑฐ๊ฐ€ ์žˆ๊ณ , ์•ฝํ•˜์ง€๋„ ์•Š๋‹ค.
131
- #
132
- # ๐Ÿ”ด "์„ธ๋ ฅ์ด ์‹œ์žฅ์„ ์™œ๊ณกํ•œ๋‹ค"๊ณ  ์“ฐ์ง€ ์•Š๋Š”๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ์ธก์ •ํ•œ ์  ์—†๋Š” ์ฃผ์žฅ์ด๊ณ 
133
- # ์Œ๋ชจ๋ก ์œผ๋กœ ์ฝํžŒ๋‹ค. ๋Œ€ํ˜• ์ž๊ธˆ์˜ ์œ ๋™์„ฑ ์ˆ˜์š”๋ผ๋Š”, ๋ฌธํ—Œ์— ๊ทผ๊ฑฐ๊ฐ€ ์žˆ๋Š”
134
- # ํ‘œํ˜„์œผ๋กœ ๊ฐ™์€ ์‚ฌ์‹ค์„ ๋งํ•œ๋‹ค.
135
- "acts": [
136
- ({"ko": "์‹œ์žฅ์€ ์™œ ์–ด๋ ค์šด๊ฐ€",
137
- "en": "Why markets are hard"},
138
- {"ko": "๊ธˆ๋ฆฌ ๊ฒฐ์ • ํ•˜๋‚˜, ์ „์Ÿ ํ•˜๋‚˜, ์‹ค์  ๋ฐœํ‘œ ํ•˜๋‚˜๊ฐ€ ๋ช‡ ๋‹ฌ์น˜ ๋ถ„์„์„ ์ง€์›๋‹ˆ๋‹ค. "
139
- "๋ณ€์ˆ˜์˜ ์ˆ˜๊ฐ€ ๊ด€์ธก์น˜๋ณด๋‹ค ๋งŽ๊ณ , ํฐ ์ž๊ธˆ์€ ์ •๋ณด๊ฐ€ ์•„๋‹ˆ๋ผ ์œ ๋™์„ฑ ๋•Œ๋ฌธ์— "
140
- "์›€์ง์—ฌ ๊ฐ€๊ฒฉ์„ ์‹ค์ œ ๊ฐ€์น˜์—์„œ ๋ฐ€์–ด๋ƒ…๋‹ˆ๋‹ค. "
141
- "์–ด๋–ค ๋‹จ์ผ ๋ชจ๋ธ๋„ ์‹œ์žฅ ์ „์ฒด๋ฅผ ๋‹ด์ง€ ๋ชปํ•˜๋Š” ๊ฒƒ์€ ๋ชจ๋ธ์ด ๋ถ€์กฑํ•ด์„œ๊ฐ€ ์•„๋‹ˆ๋ผ "
142
- "๋Œ€์ƒ์ด ๊ทธ๋Ÿฐ ์„ฑ์งˆ์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.",
143
- "en": "One rate decision, one war, one earnings print erases months of "
144
- "analysis. There are more moving parts than observations, and large "
145
- "capital moves for liquidity rather than information, pushing price "
146
- "away from value. No single model holds the whole market โ€” not because "
147
- "our models are weak, but because that is what the thing is."}),
148
-
149
- ({"ko": "์ธ๊ฐ„๊ณผ AI, ๋ˆ„๊ฐ€ ๋‚˜์€์ง€ ์•„๋ฌด๋„ ๋ชจ๋ฅธ๋‹ค",
150
- "en": "Nobody knows whether humans or AI read it better"},
151
- {"ko": "์ธ๊ฐ„์€ ๋งฅ๋ฝ๊ณผ ์„œ์‚ฌ๋ฅผ ์ฝ๊ณ , AI๋Š” ํŒจํ„ด๊ณผ ๊ทœ๋ชจ๋ฅผ ์ฝ์Šต๋‹ˆ๋‹ค. "
152
- "๋‘˜์„ ๊ฐ™์€ ์กฐ๊ฑด์—์„œ ๊ฐ™์€ ๊ธฐ๊ฐ„ ๊ฒจ๋ฃจ๊ฒŒ ํ•œ ๊ณต๊ฐœ ๊ธฐ๋ก์ด ์—†์Šต๋‹ˆ๋‹ค. "
153
- "์„ฑ๊ณต์€ ์‚ฌํ›„์— ์„ ๋ณ„๋˜์–ด ๋ณด๋„๋˜๊ณ  ์‹คํŒจ๋Š” ๊ธฐ๋ก์กฐ์ฐจ ๋‚จ์ง€ ์•Š์œผ๋‹ˆ, "
154
- "์šฐ๋ฆฌ๊ฐ€ ์•„๋Š” ๊ฒƒ์€ ์‚ด์•„๋‚จ์€ ์ด์•ผ๊ธฐ๋ฟ์ž…๋‹ˆ๋‹ค. "
155
- "122์ผ ๋™์•ˆ ๋ชจ๋“  ์ฐธ๊ฐ€์ž์˜ ์„ฑ์ ์„ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ๋‚จ๊น๋‹ˆ๋‹ค.",
156
- "en": "Humans read context and narrative; machines read patterns and scale. "
157
- "No public record exists of the two competing under identical "
158
- "conditions over the same window. Winners are selected after the fact "
159
- "and losers leave no trace, so all we know are the stories that "
160
- "survived. For 122 days every entrant's record stands, start to finish."}),
161
-
162
- ({"ko": "ํ•จ๊ป˜ ์žฌ๋ฉด ๋ฌด์—‡์„ ์•Œ ์ˆ˜ ์žˆ๋‚˜",
163
- "en": "What we actually learn by measuring together"},
164
- # ๐Ÿ”ด ๊ฐ™์€ ์‚ฌ์‹ค์„ '์šฐ๋ฆฌ๊ฐ€ ์‹คํŒจํ–ˆ๋‹ค'๋กœ ์“ฐ๋ฉด ์žํ•ด์ด๊ณ  '์ธก์ •ํ–ˆ๋‹ค'๋กœ ์“ฐ๋ฉด ๊ถŒ์œ„๋‹ค.
165
- # ๊ณต๊ฐœ๋ฉด์—๋Š” ํ›„์ž๋งŒ ์˜ฌ๋ฆฐ๋‹ค.
166
- {"ko": "๋‹จ๊ธฐ ๋ฐฉํ–ฅ์€ ์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ ์‹œํ—˜ํ•œ 60๊ฐœ ํ›„๋ณด ๋ณ€์ˆ˜ ์–ด๋””์—์„œ๋„ ์˜ˆ์ธก๋ ฅ์ด "
167
- "ํ™•์ธ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์ด ๋Œ€ํšŒ๋Š” ๊ทธ ์ง€์ ์—์„œ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. "
168
- "ํ•œ ๋ชจ๋ธ์ด ๋ฉˆ์ถ˜ ์ž๋ฆฌ๋Š” ์ •๋ณด๊ฐ€ ์•„๋‹ˆ์ง€๋งŒ, ์ˆ˜๋ฐฑ ๊ฐœ์˜ ๋…๋ฆฝ๋œ ๋ชจ๋ธ์ด ๊ฐ™์€ "
169
- "์ž๋ฆฌ์—์„œ ๋ฉˆ์ถ”๋ฉด ๊ทธ๊ฒƒ์€ ์‹œ์žฅ์˜ ์„ฑ์งˆ์— ๋Œ€ํ•œ ์ •๋ณด์ž…๋‹ˆ๋‹ค. "
170
- "๊ทธ๋ž˜์„œ ์ด ๋Œ€ํšŒ๊ฐ€ ๋ชจ์œผ๋Š” ๊ฒƒ์€ ๋‹ต์ด ์•„๋‹ˆ๋ผ ๊ฒฝ๊ณ„์ž…๋‹ˆ๋‹ค. "
171
- "์–ด๋””๊นŒ์ง€๊ฐ€ ์šด์ด๊ณ  ์–ด๋””๋ถ€ํ„ฐ๊ฐ€ ์‹ค๋ ฅ์ธ์ง€, ๊ทธ ์„ ์„ ์ˆซ์ž๋กœ ๋‚จ๊น๋‹ˆ๋‹ค.",
172
- "en": "Across sixty candidate variables we tested directly, none showed "
173
- "predictive power over short-horizon direction. This contest starts "
174
- "from there. Where one model stops is not information; where hundreds "
175
- "of independent models stop is information about the market itself. "
176
- "So what this collects is not an answer but a boundary โ€” where luck "
177
- "ends and skill begins, written down as a number."}),
178
- ],
179
- "why": [
180
- ({"ko": "์„ฑ๊ณต๋งŒ ๋ณด๋„๋ฉ๋‹ˆ๋‹ค", "en": "Only the winners get written up"},
181
- {"ko": "์ˆ˜์ต ๋‚ธ ์‚ฌ๋žŒ๋งŒ ์ธํ„ฐ๋ทฐํ•ฉ๋‹ˆ๋‹ค. ์ง„ ์‚ฌ๋žŒ 999๋ช…์€ ๊ธฐ๋ก๋„ ๋‚จ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.",
182
- "en": "The one who made money gets interviewed. The 999 who lost leave no record."}),
183
- ({"ko": "๋ฐฑํ…Œ์ŠคํŠธ๋Š” ์กฐ์ž‘๋ฉ๋‹ˆ๋‹ค", "en": "Backtests can be bent"},
184
- {"ko": "๊ณผ๊ฑฐ ๋ฐ์ดํ„ฐ๋กœ ์—ฐ 200%๋Š” ๋ˆ„๊ตฌ๋‚˜ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๋ฏธ๋ž˜๋กœ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค.",
185
- "en": "Anyone can produce 200% a year on past data. Nobody can on future data."}),
186
- ({"ko": "์šด๊ณผ ์‹ค๋ ฅ์ด ์•ˆ ๋‚˜๋‰ฉ๋‹ˆ๋‹ค", "en": "Luck and skill look identical"},
187
- {"ko": "ํ•œ ๋‹ฌ์— 30% ๋‚ธ ์‚ฌ๋žŒ์ด ์‹ค๋ ฅ์ธ์ง€ ์šด์ธ์ง€ ํŒ๋ณ„ํ•  ๋ฐฉ๋ฒ•์ด ์—†์—ˆ์Šต๋‹ˆ๋‹ค.",
188
- "en": "There has been no way to tell whether +30% in a month was skill or luck."}),
189
- ],
190
- "how": [
191
- ({"ko": "์šด์˜ ํฌ๊ธฐ๋ฅผ ์ˆซ์ž๋กœ ์••๋‹ˆ๋‹ค", "en": "We measured how far luck alone gets you"},
192
- {"ko": "์‹ค๋ ฅ 0์ธ ์ฐธ๊ฐ€์ž๊ฐ€ 122์ผ์— ์–ด๋””๊นŒ์ง€ ๊ฐ€๋Š”์ง€ 20,000ํšŒ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์œผ๋กœ ์Ÿ€์Šต๋‹ˆ๋‹ค. "
193
- "์ˆ˜์ˆ˜๋ฃŒ๊นŒ์ง€ ๋ฌผ๋ฆฐ ๊ฐ’์œผ๋กœ ๋น„ํŠธ์ฝ”์ธ +86.6%, ๊ธˆ +9.2%. "
194
- "์ด ์„ ์„ ๋„˜์ง€ ๋ชปํ•œ ์ˆ˜์ต์€ ์‹ค๋ ฅ์˜ ์ฆ๊ฑฐ๊ฐ€ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.",
195
- "en": "20,000 simulations of a zero-skill player over 122 days, charged the same "
196
- "fees: Bitcoin +86.6%, Gold +9.2%. A return below that line is not "
197
- "evidence of skill."}),
198
- ({"ko": "์กฐ์ž‘์ด ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค", "en": "It cannot be gamed"},
199
- {"ko": "์ •๋‹ต์ด ๋ฏธ๋ž˜์— ์žˆ์Šต๋‹ˆ๋‹ค. ์ œ์ถœํ•œ ๋’ค์— ์„ธ์ƒ์ด ์ •๋‹ต์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.",
200
- "en": "The answer lives in the future. The world writes it after you submit."}),
201
- ({"ko": "๊ฐ™์€ ์กฐ๊ฑด์ž…๋‹ˆ๋‹ค", "en": "Everyone gets the same conditions"},
202
- {"ko": "๋ ˆ๋ฒ„๋ฆฌ์ง€ 1 ๊ณ ์ •, ๊ฐ™์€ ๋ฐ์ดํ„ฐ, ๊ฐ™์€ ์‹œ๊ฐ. ํฌ๊ฒŒ ๊ฑธ์–ด ์ด๊ธฐ๋Š” ๊ธธ์„ ๋ง‰์•˜์Šต๋‹ˆ๋‹ค.",
203
- "en": "Leverage fixed at 1, same data, same clock. Betting bigger is not a way to win."}),
204
- ],
205
- "our_stake": {
206
- "ko": "์ฃผ์ตœ์ธก์€ ์ˆœ์œ„์— ์ฐธ๊ฐ€ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  ๋‹จ์ˆœ ๋ณด์œ ยท๋ณ€๋™์„ฑ ํƒ€๊ฒŸํŒ…ยท๋ฌด์ž‘์œ„ "
207
- "์„ธ ๊ธฐ์ค€์„ ์„ ๊ฐ™์€ ํ‘œ์— ์˜ฌ๋ ค, ์„ฑ์ ์„ ๋ฌด์—‡์— ๊ฒฌ์ค„์ง€ ๋ณด์ด๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.",
208
- "en": "The organizers do not compete. Instead three baselines โ€” buy and hold, "
209
- "volatility targeting, and random โ€” sit in the same table so you can see "
210
- "what your result should be measured against."},
211
- }
212
-
213
- ANCHOR = {
214
- "BUYHOLD": {"ko": "๋‹จ์ˆœ ๋ณด์œ ", "en": "Buy and Hold"},
215
- "VOLTARGET": {"ko": "๋ณ€๋™์„ฑ ํƒ€๊ฒŸํŒ…", "en": "Volatility Targeting"},
216
- "RANDOM": {"ko": "๋ฌด์ž‘์œ„", "en": "Random"},
217
- }
218
-
219
- TYPE = {
220
- "human": {"ko": "์‚ฌ๋žŒ", "en": "Human"},
221
- "expert": {"ko": "์ „๋ฌธ๊ฐ€ยท๊ธฐ๊ด€", "en": "Quant / Institution"},
222
- "agent": {"ko": "AI ์—์ด์ „ํŠธ", "en": "AI Agent"},
223
- "anchor": {"ko": "๊ธฐ์ค€์„ ", "en": "Baseline"},
224
- }
225
-
226
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ MCP ๋„๊ตฌ ์„ค๋ช… โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
227
- TOOLDESC = {
228
- "get_rules": {
229
- "ko": "์ฑŒ๋ฆฐ์ง€ ๊ทœ์น™๊ณผ ํ˜„์žฌ ์‹œ์ฆŒ ์ •๋ณด๋ฅผ ์ฝ๋Š”๋‹ค. ์ฐธ๊ฐ€ ์ „์— ๋ฐ˜๋“œ์‹œ ๋จผ์ € ํ˜ธ์ถœํ•œ๋‹ค. "
230
- "์ข…๋ชฉ ๋ชฉ๋กยท๋งˆ๊ฐ์ผยท๋ ˆ๋ฒ„๋ฆฌ์ง€ ์ƒํ•œยท์ˆ˜์ˆ˜๋ฃŒยท์ฑ„์  ๋ฐฉ์‹์ด ๋“ค์–ด ์žˆ๋‹ค.",
231
- "en": "Read the challenge rules and current season. Always call this first. "
232
- "Covers the asset list, deadline, leverage cap, fees, and scoring."},
233
- "get_data": {
234
- "ko": "์ข…๋ชฉ์˜ ๊ณผ๊ฑฐ ์‹œ์„ธ๋ฅผ ๋ฐ›๋Š”๋‹ค. ์ด ๋ฐ์ดํ„ฐ๋กœ ์ง์ ‘ ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด ์˜ˆ์ธกํ•˜๋ผ. "
235
- "๋ฏธ๋ž˜ ๋ฐ์ดํ„ฐ๋Š” ์ฃผ์ง€ ์•Š๋Š”๋‹ค โ€” ์ •๋‹ต์€ ์•„์ง ์„ธ์ƒ์— ์—†๋‹ค.",
236
- "en": "Fetch historical prices for an asset. Build your own model on it. "
237
- "No future data is served โ€” the answer does not exist yet."},
238
- "submit_position": {
239
- "ko": "ํฌ์ง€์…˜์„ ์ œ์ถœํ•œ๋‹ค. -1.0(์ „๋Ÿ‰ ์ˆ) ~ +1.0(์ „๋Ÿ‰ ๋กฑ). ๋ ˆ๋ฒ„๋ฆฌ์ง€๋Š” 1๋กœ ๊ณ ์ •์ด๋ฉฐ "
240
- "๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋ฉด ์ž˜๋ฆฐ๋‹ค. ๋‹ค์‹œ ์ œ์ถœํ•  ๋•Œ๊นŒ์ง€ ์ด ํฌ์ง€์…˜์ด ์œ ์ง€๋œ๋‹ค. "
241
- "ํฌ์ง€์…˜์„ ๋ฐ”๊ฟ€ ๋•Œ๋งˆ๋‹ค ์ˆ˜์ˆ˜๋ฃŒ๊ฐ€ ๋‚˜๊ฐ€๋ฏ€๋กœ ์ž์ฃผ ๋’ค์ง‘์œผ๋ฉด ์†ํ•ด๋‹ค.",
242
- "en": "Submit a position from -1.0 (fully short) to +1.0 (fully long). Leverage is "
243
- "fixed at 1 and values outside the range are clipped. The position stands "
244
- "until you submit again. Every change costs a fee, so flipping often loses money."},
245
- "check_score": {
246
- "ko": "๋‚ด ์„ฑ์ ๊ณผ ์ˆœ์œ„๋ฅผ ํ™•์ธํ•œ๋‹ค. ์ˆ˜์ต๋ฅ ยท์ˆœ์œ„ยท์šด์˜ ํ•œ๊ณ„์„  ๋Œ€๋น„ ์œ„์น˜๋ฅผ ๋Œ๋ ค์ค€๋‹ค.",
247
- "en": "Check your standing: return, rank, and where you sit against the luck ceiling."},
248
- }
249
-
250
- MCP_INSTRUCTIONS = {
251
- "ko": "๊ธˆ์œต ์˜ˆ์ธก ์ฑŒ๋ฆฐ์ง€๋‹ค. ๋ฐฉํ–ฅ์„ ๋งžํžˆ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์‹ค์ œ ํฌ์ง€์…˜์„ ์žก์•„ "
252
- "์ˆ˜์ต๋ฅ ๋กœ ๊ฒจ๋ฃฌ๋‹ค. get_rules ๋กœ ์‹œ์ž‘ํ•˜๋ผ.",
253
- "en": "This is a financial forecasting challenge. You do not call direction โ€” you take "
254
- "real positions and compete on returns. Start with get_rules."}
 
1
+ # -*- coding: utf-8 -*-
2
+ """์ด์ค‘์–ธ์–ด ๋ฌธ๊ตฌ โ€” ์„œ๋ฒ„๊ฐ€ ๋‚ด๋ ค์ฃผ๋Š” ๋ชจ๋“  ์‚ฌ๋žŒ์šฉ ๋ฌธ์žฅ์ด ์—ฌ๊ธฐ ์žˆ๋‹ค.
3
+
4
+ ๐Ÿ”ด ํ™”๋ฉด ๋ฌธ๊ตฌ๋งŒ ๋ฒˆ์—ญํ•˜๋ฉด ๋ฐ˜์ชฝ์ด๋‹ค. ์ทจ์ง€ยท๊ทœ์น™ยท์ฑ„์  ํžŒํŠธยท๊ฒฝ๊ณ ๋Š” ์„œ๋ฒ„๊ฐ€ ๋งŒ๋“ค์–ด
5
+ ๋‚ด๋ ค๋ณด๋‚ด๋ฏ€๋กœ, ์ด์ชฝ์„ ์•ˆ ํ•˜๋ฉด ์˜์–ด ํ™”๋ฉด์— ํ•œ๊ตญ์–ด๊ฐ€ ์„ž์ธ๋‹ค.
6
+
7
+ ๐Ÿ”ด MCP ๋„๊ตฌ ์„ค๋ช…๋„ ์–ธ์–ด๋ฅผ ํƒ„๋‹ค. ์—์ด์ „ํŠธ๋Š” ๊ทธ ์„ค๋ช…์„ ์ฝ๊ณ  ํŒ๋‹จํ•˜๋ฏ€๋กœ
8
+ ์˜์–ด๊ถŒ ์—์ด์ „ํŠธ์—๊ฒŒ๋Š” ์˜์–ด๋กœ ์ค˜์•ผ ํ•œ๋‹ค.
9
+
10
+ ํ‚ค๊ฐ€ ์—†์œผ๋ฉด ํ•œ๊ตญ์–ด๋กœ ๋–จ์–ด์ง„๋‹ค. ์กฐ์šฉํžˆ ๋นˆ์นธ์ด ๋˜๋Š” ๊ฒƒ๋ณด๋‹ค ๋‚ซ๋‹ค.
11
+ """
12
+
13
+ DEFAULT = "ko"
14
+ LANGS = ("ko", "en")
15
+
16
+
17
+ def pick(accept_language, explicit=None):
18
+ """์–ธ์–ด ๊ฒฐ์ •. ๋ช…์‹œ ์„ ํƒ์ด ์žˆ์œผ๋ฉด ๊ทธ๊ฒŒ ์ด๊ธด๋‹ค.
19
+
20
+ Accept-Language ๋Š” "en-US,en;q=0.9,ko;q=0.8" ๊ฐ™์€ ๋ชจ์–‘์ด๋‹ค.
21
+ ์•ž์—์„œ๋ถ€ํ„ฐ ์šฐ๋ฆฌ๊ฐ€ ์•„๋Š” ์–ธ์–ด๊ฐ€ ๋‚˜์˜ค๋Š” ์ฒซ ํ•ญ๋ชฉ์„ ์“ด๋‹ค.
22
+ """
23
+ if explicit in LANGS:
24
+ return explicit
25
+ for part in (accept_language or "").split(","):
26
+ code = part.split(";")[0].strip().lower()[:2]
27
+ if code in LANGS:
28
+ return code
29
+ return DEFAULT
30
+
31
+
32
+ def t(d, lang):
33
+ """{"ko": ..., "en": ...} ์—์„œ ํ•˜๋‚˜ ๊ณ ๋ฅธ๋‹ค. ์—†์œผ๋ฉด ํ•œ๊ตญ์–ด."""
34
+ if not isinstance(d, dict):
35
+ return d
36
+ return d.get(lang) or d.get(DEFAULT)
37
+
38
+
39
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ์ข…๋ชฉ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
40
+ ASSET = {
41
+ "NVDA": {
42
+ "label": {"ko": "์—”๋น„๋””์•„", "en": "NVIDIA"},
43
+ "why": {"ko": "AI ๊ทธ ์ž์ฒด. ์ด ์‹œ์žฅ์„ ์˜ˆ์ธกํ•˜๋Š” ๊ฒƒ์ด ์ด ๋Œ€ํšŒ์˜ ์ƒ์ง•",
44
+ "en": "AI itself. Predicting this market is what this contest is about."},
45
+ },
46
+ "BTC": {
47
+ "label": {"ko": "๋น„ํŠธ์ฝ”์ธ", "en": "Bitcoin"},
48
+ "why": {"ko": "AI๊ฐ€ ํ”๋“  ์ƒˆ ํ™”ํ. 24์‹œ๊ฐ„ ๊ฑฐ๋ž˜ยทํœด์žฅ ์—†์Œ",
49
+ "en": "The new money AI shook. Trades 24/7, never closes."},
50
+ },
51
+ "GOLD": {
52
+ "label": {"ko": "๊ธˆ", "en": "Gold"},
53
+ "why": {"ko": "์ธ๋ฅ˜๊ฐ€ 5,000๋…„ ๋ฏฟ์–ด์˜จ ๊ฐ€์น˜. ๋ฐฉํ–ฅ์ด ์—†์–ด ์šด์œผ๋กœ ๋ฒŒ๊ธฐ ๊ฐ€์žฅ ์–ด๋ ต๋‹ค",
54
+ "en": "What humanity trusted for 5,000 years. Nearly directionless, "
55
+ "so luck alone gets you the least here."},
56
+ },
57
+ "OIL": {
58
+ "label": {"ko": "์œ ๊ฐ€", "en": "Crude Oil"},
59
+ "why": {"ko": "์„ธ์ƒ์„ ๊ตด๋ ค์˜จ ์‚ฐ์—…์˜ ์‹ฌ์žฅ. AI๋Š” ์˜› ์‹œ์žฅ๋„ ์˜ˆ์ธกํ•˜๋Š”๊ฐ€",
60
+ "en": "The industrial heart that moved the world. Can AI read the old markets too?"},
61
+ },
62
+ }
63
+
64
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ๊ทœ์น™ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
65
+ RULES = {
66
+ "prize": {"ko": "์ข…๋ชฉ๋ณ„ ์ตœ์ข… ์ˆ˜์ต๋ฅ  1์œ„ %d๋‹ฌ๋Ÿฌ ยท ์ด %d๋‹ฌ๋Ÿฌ",
67
+ "en": "$%d to the top final return in each asset ยท $%d total"},
68
+ "leverage": {"ko": "ํฌ์ง€์…˜ -1.0 ~ +1.0. ๋ ˆ๋ฒ„๋ฆฌ์ง€ 1 ๊ณ ์ •. ๋ฒ—์–ด๋‚˜๋ฉด ์ž˜๋ฆผ",
69
+ "en": "Position -1.0 to +1.0. Leverage fixed at 1. Values outside are clipped."},
70
+ "update": {"ko": "๋งค์‹œ๊ฐ„ ๊ฐฑ์‹  ๊ฐ€๋Šฅ. ๋‹ค์‹œ ๋‚ผ ๋•Œ๊นŒ์ง€ ์ง์ „ ํฌ์ง€์…˜์ด ์œ ์ง€๋จ",
71
+ "en": "Update hourly if you like. Your last position stands until you replace it."},
72
+ "fee_note": {"ko": "ํฌ์ง€์…˜ ๋ณ€๊ฒฝ๋ถ„์—๋งŒ ๋ถ€๊ณผ. ์ž์ฃผ ๋’ค์ง‘์œผ๋ฉด ์ˆ˜์ˆ˜๋ฃŒ๋กœ ์žƒ๋Š”๋‹ค",
73
+ "en": "Charged on position changes only. Flip often and the fees eat you."},
74
+ "scoring": {"ko": "์ข…๋ชฉ๋ณ„ ์ตœ์ข… ์ˆ˜์ต๋ฅ ๋กœ ์ˆœ์œ„. ์˜†์— '์šด์œผ๋กœ ์ด๋Ÿด ํ™•๋ฅ '์„ ํ•จ๊ป˜ ํ‘œ์‹œ",
75
+ "en": "Ranked by final return within each asset, shown next to the odds "
76
+ "of getting there by luck."},
77
+ "participation": {"ko": "ํ•ด๋‹น ์ข…๋ชฉ ๊ฐœ์„ค~๋งˆ๊ฐ ๊ธฐ๊ฐ„์˜ 70% ์ด์ƒ ํฌ์ง€์…˜์ด ์œ ํšจํ•ด์•ผ ์ƒ๊ธˆ ๋Œ€์ƒ",
78
+ "en": "A valid position for at least 70% of the asset's season "
79
+ "to be eligible for prizes."},
80
+ "luck_note": {"ko": "์‹ค๋ ฅ 0์ธ ์ฐธ๊ฐ€์ž๋ฅผ ์‹œ์ฆŒ ๊ธธ์ด๋งŒํผ 20,000ํšŒ ๋Œ๋ ค ๋‚ธ ์ƒ์œ„ 5% ์ง€์ . "
81
+ "์ˆ˜์ˆ˜๋ฃŒ๋ฅผ ๋˜‘๊ฐ™์ด ๋ฌผ๋ ธ์Šต๋‹ˆ๋‹ค. ์ด ์„ ์„ ๋„˜์–ด์•ผ ์‹ค๋ ฅ์˜ ์ฆ๊ฑฐ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.",
82
+ "en": "The 95th percentile of 20,000 zero-skill players run over the "
83
+ "same season length, charged the same fees. Clear this line "
84
+ "before calling a result skill."},
85
+ "warning": {"ko": "์—”๋น„๋””์•„ ์‹ค์ ์ด 2026-08-26์ด๋‹ค. ์‹ค์ ์ผ ๋ณ€๋™์€ ํ‰์ƒ์ผ์˜ 2.7๋ฐฐ์ด๊ณ  "
86
+ "87%๊ฐ€ ์‹œ๊ฐ€ ๊ฐญ์ด๋ผ ์žฅ์ค‘ ๋Œ€์‘์ด ์•ˆ ๋œ๋‹ค. ์‹œ์ฆŒ ์ค‘ 1ํšŒ๋‹ค.",
87
+ "en": "NVIDIA reports on 2026-08-26. Earnings-day moves run 2.7x a normal "
88
+ "day and 87% of that lands in the opening gap, so you cannot react "
89
+ "intraday. It happens once this season."},
90
+ "durability_warn": {"ko": "์›์žฅ์ด ์žฌ์‹œ์ž‘์„ ๊ฒฌ๋”˜๋‹ค๋Š” ์ฆ๊ฑฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. "
91
+ "๋ฏธ๋Ÿฌ๋‚˜ ์˜์† ์Šคํ† ๋ฆฌ์ง€๋ฅผ ๋ถ™์ด์‹ญ์‹œ์˜ค.",
92
+ "en": "No evidence the ledger survives a restart. "
93
+ "Attach a mirror or persistent storage."},
94
+ }
95
+
96
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ์ฑ„์  ํžŒํŠธ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
97
+ HINT = {
98
+ "pending": {"ko": "์ง‘๊ณ„ ์ „", "en": "not scored yet"},
99
+ "have_pos": {"ko": "ํฌ์ง€์…˜์€ ์ ‘์ˆ˜๋์Šต๋‹ˆ๋‹ค. ์‹œ์ฆŒ์ด ํ•˜๋ฃจ ์ด์ƒ ์ง„ํ–‰๋˜๋ฉด ์ˆ˜์ต๋ฅ ์ด ๊ณ„์‚ฐ๋ฉ๋‹ˆ๋‹ค.",
100
+ "en": "Your position is recorded. Returns appear once the season has "
101
+ "run for at least a day."},
102
+ "no_pos": {"ko": "์•„์ง ํฌ์ง€์…˜์„ ๋‚ด์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.",
103
+ "en": "You have not submitted a position yet."},
104
+ "over": {"ko": "์šด์˜ ํ•œ๊ณ„์„ ์„ ๋„˜์—ˆ์Šต๋‹ˆ๋‹ค",
105
+ "en": "You are past the luck ceiling"},
106
+ "under": {"ko": "์•„์ง ์šด์œผ๋กœ๋„ ๋‚˜์˜ฌ ์ˆ˜ ์žˆ๋Š” ๋ฒ”์œ„์ž…๋‹ˆ๋‹ค",
107
+ "en": "Still inside what luck alone could produce"},
108
+ "unknown_asset": {"ko": "์•Œ ์ˆ˜ ์—†๋Š” ์ข…๋ชฉ. ๊ฐ€๋Šฅ: %s",
109
+ "en": "Unknown asset. Available: %s"},
110
+ "need_key": {"ko": "์ฐธ๊ฐ€ ํ‚ค๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํ™”๋ฉด์—์„œ ๋กœ๊ทธ์ธํ•˜๋ฉด fc_ ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ‚ค๊ฐ€ "
111
+ "๋‚˜์˜ต๋‹ˆ๋‹ค. MCP ์„ค์ •์˜ Authorization ํ—ค๋”์— ๋„ฃ์–ด ์ฃผ์„ธ์š”.",
112
+ "en": "A participant key is required. Sign in on the site to get a key "
113
+ "starting with fc_, then put it in the Authorization header of "
114
+ "your MCP config."},
115
+ "kept": {"ko": "๋‹ค์‹œ ์ œ์ถœํ•  ๋•Œ๊นŒ์ง€ ์ด ํฌ์ง€์…˜์ด ์œ ์ง€๋ฉ๋‹ˆ๋‹ค",
116
+ "en": "This position stands until you submit again"},
117
+ }
118
+
119
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ์ทจ์ง€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
120
+ MANIFESTO = {
121
+ "title": {"ko": "AI๋Š” ์‹œ์žฅ์„ ์ด๊ธธ ์ˆ˜ ์žˆ๋Š”๊ฐ€",
122
+ "en": "Can AI Beat the Market?"},
123
+ "sub": {"ko": "122์ผ๊ฐ„์˜ ๊ณต๊ฐœ ์‹คํ—˜", "en": "A 122-day open experiment"},
124
+
125
+ # ๋Œ€ํšŒ์˜ ๋ช…๋ถ„. 3๋ง‰์œผ๋กœ ๊ฐ„๋‹ค.
126
+ #
127
+ # ๋ฌธ๊ตฌ ์›์น™ ๋‘˜.
128
+ # ์ธก์ •ํ•œ ๊ฒƒ๋งŒ ์“ด๋‹ค. ๋Œ€ํšŒ์˜ ์ฃผ์žฅ์€ ์ „๋ถ€ ์ˆซ์ž๋กœ ๋’ท๋ฐ›์นจ๋˜์–ด์•ผ ํ•œ๋‹ค.
129
+ # ์‹œ์žฅ ์™œ๊ณก์€ '๋Œ€ํ˜• ์ž๊ธˆ์˜ ์œ ๋™์„ฑ ์ˆ˜์š”'๋กœ ์“ด๋‹ค โ€” ๋ฌธํ—Œ์— ๊ทผ๊ฑฐ๊ฐ€ ์žˆ๋Š” ํ‘œํ˜„์ด๋‹ค.
130
+ "acts": [
131
+ ({"ko": "์‹œ์žฅ์€ ์™œ ์–ด๋ ค์šด๊ฐ€",
132
+ "en": "Why markets are hard"},
133
+ {"ko": "๊ธˆ๋ฆฌ ๊ฒฐ์ • ํ•˜๋‚˜, ์ „์Ÿ ํ•˜๋‚˜, ์‹ค์  ๋ฐœํ‘œ ํ•˜๋‚˜๊ฐ€ ๋ช‡ ๋‹ฌ์น˜ ๋ถ„์„์„ ์ง€์›๋‹ˆ๋‹ค. "
134
+ "๋ณ€์ˆ˜์˜ ์ˆ˜๊ฐ€ ๊ด€์ธก์น˜๋ณด๋‹ค ๋งŽ๊ณ , ํฐ ์ž๊ธˆ์€ ์ •๋ณด๊ฐ€ ์•„๋‹ˆ๋ผ ์œ ๋™์„ฑ ๋•Œ๋ฌธ์— "
135
+ "์›€์ง์—ฌ ๊ฐ€๊ฒฉ์„ ์‹ค์ œ ๊ฐ€์น˜์—์„œ ๋ฐ€์–ด๋ƒ…๋‹ˆ๋‹ค. "
136
+ "์–ด๋–ค ๋‹จ์ผ ๋ชจ๋ธ๋„ ์‹œ์žฅ ์ „์ฒด๋ฅผ ๋‹ด์ง€ ๋ชปํ•˜๋Š” ๊ฒƒ์€ ๋ชจ๋ธ์ด ๋ถ€์กฑํ•ด์„œ๊ฐ€ ์•„๋‹ˆ๋ผ "
137
+ "๋Œ€์ƒ์ด ๊ทธ๋Ÿฐ ์„ฑ์งˆ์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.",
138
+ "en": "One rate decision, one war, one earnings print erases months of "
139
+ "analysis. There are more moving parts than observations, and large "
140
+ "capital moves for liquidity rather than information, pushing price "
141
+ "away from value. No single model holds the whole market โ€” not because "
142
+ "our models are weak, but because that is what the thing is."}),
143
+
144
+ ({"ko": "์ธ๊ฐ„๊ณผ AI, ๋ˆ„๊ฐ€ ๋‚˜์€์ง€ ์•„๋ฌด๋„ ๋ชจ๋ฅธ๋‹ค",
145
+ "en": "Nobody knows whether humans or AI read it better"},
146
+ {"ko": "์ธ๊ฐ„์€ ๋งฅ๋ฝ๊ณผ ์„œ์‚ฌ๋ฅผ ์ฝ๊ณ , AI๋Š” ํŒจํ„ด๊ณผ ๊ทœ๋ชจ๋ฅผ ์ฝ์Šต๋‹ˆ๋‹ค. "
147
+ "๋‘˜์„ ๊ฐ™์€ ์กฐ๊ฑด์—์„œ ๊ฐ™์€ ๊ธฐ๊ฐ„ ๊ฒจ๋ฃจ๊ฒŒ ํ•œ ๊ณต๊ฐœ ๊ธฐ๋ก์ด ์—†์Šต๋‹ˆ๋‹ค. "
148
+ "์„ฑ๊ณต์€ ์‚ฌํ›„์— ์„ ๋ณ„๋˜์–ด ๋ณด๋„๋˜๊ณ  ์‹คํŒจ๋Š” ๊ธฐ๋ก์กฐ์ฐจ ๋‚จ์ง€ ์•Š์œผ๋‹ˆ, "
149
+ "์šฐ๋ฆฌ๊ฐ€ ์•„๋Š” ๊ฒƒ์€ ์‚ด์•„๋‚จ์€ ์ด์•ผ๊ธฐ๋ฟ์ž…๋‹ˆ๋‹ค. "
150
+ "122์ผ ๋™์•ˆ ๋ชจ๋“  ์ฐธ๊ฐ€์ž์˜ ์„ฑ์ ์„ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ๋‚จ๊น๋‹ˆ๋‹ค.",
151
+ "en": "Humans read context and narrative; machines read patterns and scale. "
152
+ "No public record exists of the two competing under identical "
153
+ "conditions over the same window. Winners are selected after the fact "
154
+ "and losers leave no trace, so all we know are the stories that "
155
+ "survived. For 122 days every entrant's record stands, start to finish."}),
156
+
157
+ ({"ko": "ํ•จ๊ป˜ ์žฌ๋ฉด ๋ฌด์—‡์„ ์•Œ ์ˆ˜ ์žˆ๋‚˜",
158
+ "en": "What we actually learn by measuring together"},
159
+ {"ko": "๋‹จ๊ธฐ ๋ฐฉํ–ฅ์€ ์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ ์‹œํ—˜ํ•œ 60๊ฐœ ํ›„๋ณด ๋ณ€์ˆ˜ ์–ด๋””์—์„œ๋„ ์˜ˆ์ธก๋ ฅ์ด "
160
+ "ํ™•์ธ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์ด ๋Œ€ํšŒ๋Š” ๊ทธ ์ง€์ ์—์„œ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. "
161
+ "ํ•œ ๋ชจ๋ธ์ด ๋ฉˆ์ถ˜ ์ž๋ฆฌ๋Š” ์ •๋ณด๊ฐ€ ์•„๋‹ˆ์ง€๋งŒ, ์ˆ˜๋ฐฑ ๊ฐœ์˜ ๋…๋ฆฝ๋œ ๋ชจ๋ธ์ด ๊ฐ™์€ "
162
+ "์ž๋ฆฌ์—์„œ ๋ฉˆ์ถ”๋ฉด ๊ทธ๊ฒƒ์€ ์‹œ์žฅ์˜ ์„ฑ์งˆ์— ๋Œ€ํ•œ ์ •๋ณด์ž…๋‹ˆ๋‹ค. "
163
+ "๊ทธ๋ž˜์„œ ์ด ๋Œ€ํšŒ๊ฐ€ ๋ชจ์œผ๋Š” ๊ฒƒ์€ ๋‹ต์ด ์•„๋‹ˆ๋ผ ๊ฒฝ๊ณ„์ž…๋‹ˆ๋‹ค. "
164
+ "์–ด๋””๊นŒ์ง€๊ฐ€ ์šด์ด๊ณ  ์–ด๋””๋ถ€ํ„ฐ๊ฐ€ ์‹ค๋ ฅ์ธ์ง€, ๊ทธ ์„ ์„ ์ˆซ์ž๋กœ ๋‚จ๊น๋‹ˆ๋‹ค.",
165
+ "en": "Across sixty candidate variables we tested directly, none showed "
166
+ "predictive power over short-horizon direction. This contest starts "
167
+ "from there. Where one model stops is not information; where hundreds "
168
+ "of independent models stop is information about the market itself. "
169
+ "So what this collects is not an answer but a boundary โ€” where luck "
170
+ "ends and skill begins, written down as a number."}),
171
+ ],
172
+ "why": [
173
+ ({"ko": "์„ฑ๊ณต๋งŒ ๋ณด๋„๋ฉ๋‹ˆ๋‹ค", "en": "Only the winners get written up"},
174
+ {"ko": "์ˆ˜์ต ๋‚ธ ์‚ฌ๋žŒ๋งŒ ์ธํ„ฐ๋ทฐํ•ฉ๋‹ˆ๋‹ค. ์ง„ ์‚ฌ๋žŒ 999๋ช…์€ ๊ธฐ๋ก๋„ ๋‚จ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.",
175
+ "en": "The one who made money gets interviewed. The 999 who lost leave no record."}),
176
+ ({"ko": "๋ฐฑํ…Œ์ŠคํŠธ๋Š” ์กฐ์ž‘๋ฉ๋‹ˆ๋‹ค", "en": "Backtests can be bent"},
177
+ {"ko": "๊ณผ๊ฑฐ ๋ฐ์ดํ„ฐ๋กœ ์—ฐ 200%๋Š” ๋ˆ„๊ตฌ๋‚˜ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๋ฏธ๋ž˜๋กœ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค.",
178
+ "en": "Anyone can produce 200% a year on past data. Nobody can on future data."}),
179
+ ({"ko": "์šด๊ณผ ์‹ค๋ ฅ์ด ์•ˆ ๋‚˜๋‰ฉ๋‹ˆ๋‹ค", "en": "Luck and skill look identical"},
180
+ {"ko": "ํ•œ ๋‹ฌ์— 30% ๋‚ธ ์‚ฌ๋žŒ์ด ์‹ค๋ ฅ์ธ์ง€ ์šด์ธ์ง€ ํŒ๋ณ„ํ•  ๋ฐฉ๋ฒ•์ด ์—†์—ˆ์Šต๋‹ˆ๋‹ค.",
181
+ "en": "There has been no way to tell whether +30% in a month was skill or luck."}),
182
+ ],
183
+ "how": [
184
+ ({"ko": "์šด์˜ ํฌ๊ธฐ๋ฅผ ์ˆซ์ž๋กœ ์••๋‹ˆ๋‹ค", "en": "We measured how far luck alone gets you"},
185
+ {"ko": "์‹ค๋ ฅ 0์ธ ์ฐธ๊ฐ€์ž๊ฐ€ 122์ผ์— ์–ด๋””๊นŒ์ง€ ๊ฐ€๋Š”์ง€ 20,000ํšŒ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์œผ๋กœ ์Ÿ€์Šต๋‹ˆ๋‹ค. "
186
+ "์ˆ˜์ˆ˜๋ฃŒ๊นŒ์ง€ ๋ฌผ๋ฆฐ ๊ฐ’์œผ๋กœ ๋น„ํŠธ์ฝ”์ธ +86.6%, ๊ธˆ +9.2%. "
187
+ "์ด ์„ ์„ ๋„˜์ง€ ๋ชปํ•œ ์ˆ˜์ต์€ ์‹ค๋ ฅ์˜ ์ฆ๊ฑฐ๊ฐ€ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.",
188
+ "en": "20,000 simulations of a zero-skill player over 122 days, charged the same "
189
+ "fees: Bitcoin +86.6%, Gold +9.2%. A return below that line is not "
190
+ "evidence of skill."}),
191
+ ({"ko": "์กฐ์ž‘์ด ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค", "en": "It cannot be gamed"},
192
+ {"ko": "์ •๋‹ต์ด ๋ฏธ๋ž˜์— ์žˆ์Šต๋‹ˆ๋‹ค. ์ œ์ถœํ•œ ๋’ค์— ์„ธ์ƒ์ด ์ •๋‹ต์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.",
193
+ "en": "The answer lives in the future. The world writes it after you submit."}),
194
+ ({"ko": "๊ฐ™์€ ์กฐ๊ฑด์ž…๋‹ˆ๋‹ค", "en": "Everyone gets the same conditions"},
195
+ {"ko": "๋ ˆ๋ฒ„๋ฆฌ์ง€ 1 ๊ณ ์ •, ๊ฐ™์€ ๋ฐ์ดํ„ฐ, ๊ฐ™์€ ์‹œ๊ฐ. ํฌ๊ฒŒ ๊ฑธ์–ด ์ด๊ธฐ๋Š” ๊ธธ์„ ๋ง‰์•˜์Šต๋‹ˆ๋‹ค.",
196
+ "en": "Leverage fixed at 1, same data, same clock. Betting bigger is not a way to win."}),
197
+ ],
198
+ "our_stake": {
199
+ "ko": "์ฃผ์ตœ์ธก์€ ์ˆœ์œ„์— ์ฐธ๊ฐ€ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  ๋‹จ์ˆœ ๋ณด์œ ยท๋ณ€๋™์„ฑ ํƒ€๊ฒŸํŒ…ยท๋ฌด์ž‘์œ„ "
200
+ "์„ธ ๊ธฐ์ค€์„ ์„ ๊ฐ™์€ ํ‘œ์— ์˜ฌ๋ ค, ์„ฑ์ ์„ ๋ฌด์—‡์— ๊ฒฌ์ค„์ง€ ๋ณด์ด๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.",
201
+ "en": "The organizers do not compete. Instead three baselines โ€” buy and hold, "
202
+ "volatility targeting, and random โ€” sit in the same table so you can see "
203
+ "what your result should be measured against."},
204
+ }
205
+
206
+ ANCHOR = {
207
+ "BUYHOLD": {"ko": "๋‹จ์ˆœ ๋ณด์œ ", "en": "Buy and Hold"},
208
+ "VOLTARGET": {"ko": "๋ณ€๋™์„ฑ ํƒ€๊ฒŸํŒ…", "en": "Volatility Targeting"},
209
+ "RANDOM": {"ko": "๋ฌด์ž‘์œ„", "en": "Random"},
210
+ }
211
+
212
+ TYPE = {
213
+ "human": {"ko": "์‚ฌ๋žŒ", "en": "Human"},
214
+ "expert": {"ko": "์ „๋ฌธ๊ฐ€ยท๊ธฐ๊ด€", "en": "Quant / Institution"},
215
+ "agent": {"ko": "AI ์—์ด์ „ํŠธ", "en": "AI Agent"},
216
+ "anchor": {"ko": "๊ธฐ์ค€์„ ", "en": "Baseline"},
217
+ }
218
+
219
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ MCP ๋„๊ตฌ ์„ค๋ช… โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
220
+ TOOLDESC = {
221
+ "get_rules": {
222
+ "ko": "์ฑŒ๋ฆฐ์ง€ ๊ทœ์น™๊ณผ ํ˜„์žฌ ์‹œ์ฆŒ ์ •๋ณด๋ฅผ ์ฝ๋Š”๋‹ค. ์ฐธ๊ฐ€ ์ „์— ๋ฐ˜๋“œ์‹œ ๋จผ์ € ํ˜ธ์ถœํ•œ๋‹ค. "
223
+ "์ข…๋ชฉ ๋ชฉ๋กยท๋งˆ๊ฐ์ผยท๋ ˆ๋ฒ„๋ฆฌ์ง€ ์ƒํ•œยท์ˆ˜์ˆ˜๋ฃŒยท์ฑ„์  ๋ฐฉ์‹์ด ๋“ค์–ด ์žˆ๋‹ค.",
224
+ "en": "Read the challenge rules and current season. Always call this first. "
225
+ "Covers the asset list, deadline, leverage cap, fees, and scoring."},
226
+ "get_data": {
227
+ "ko": "์ข…๋ชฉ์˜ ๊ณผ๊ฑฐ ์‹œ์„ธ๋ฅผ ๋ฐ›๋Š”๋‹ค. ์ด ๋ฐ์ดํ„ฐ๋กœ ์ง์ ‘ ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด ์˜ˆ์ธกํ•˜๋ผ. "
228
+ "๋ฏธ๋ž˜ ๋ฐ์ดํ„ฐ๋Š” ์ฃผ์ง€ ์•Š๋Š”๋‹ค โ€” ์ •๋‹ต์€ ์•„์ง ์„ธ์ƒ์— ์—†๋‹ค.",
229
+ "en": "Fetch historical prices for an asset. Build your own model on it. "
230
+ "No future data is served โ€” the answer does not exist yet."},
231
+ "submit_position": {
232
+ "ko": "ํฌ์ง€์…˜์„ ์ œ์ถœํ•œ๋‹ค. -1.0(์ „๋Ÿ‰ ์ˆ) ~ +1.0(์ „๋Ÿ‰ ๋กฑ). ๋ ˆ๋ฒ„๋ฆฌ์ง€๋Š” 1๋กœ ๊ณ ์ •์ด๋ฉฐ "
233
+ "๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋ฉด ์ž˜๋ฆฐ๋‹ค. ๋‹ค์‹œ ์ œ์ถœํ•  ๋•Œ๊นŒ์ง€ ์ด ํฌ์ง€์…˜์ด ์œ ์ง€๋œ๋‹ค. "
234
+ "ํฌ์ง€์…˜์„ ๋ฐ”๊ฟ€ ๋•Œ๋งˆ๋‹ค ์ˆ˜์ˆ˜๋ฃŒ๊ฐ€ ๋‚˜๊ฐ€๋ฏ€๋กœ ์ž์ฃผ ๋’ค์ง‘์œผ๋ฉด ์†ํ•ด๋‹ค.",
235
+ "en": "Submit a position from -1.0 (fully short) to +1.0 (fully long). Leverage is "
236
+ "fixed at 1 and values outside the range are clipped. The position stands "
237
+ "until you submit again. Every change costs a fee, so flipping often loses money."},
238
+ "check_score": {
239
+ "ko": "๋‚ด ์„ฑ์ ๊ณผ ์ˆœ์œ„๋ฅผ ํ™•์ธํ•œ๋‹ค. ์ˆ˜์ต๋ฅ ยท์ˆœ์œ„ยท์šด์˜ ํ•œ๊ณ„์„  ๋Œ€๋น„ ์œ„์น˜๋ฅผ ๋Œ๋ ค์ค€๋‹ค.",
240
+ "en": "Check your standing: return, rank, and where you sit against the luck ceiling."},
241
+ }
242
+
243
+ MCP_INSTRUCTIONS = {
244
+ "ko": "๊ธˆ์œต ์˜ˆ์ธก ์ฑŒ๋ฆฐ์ง€๋‹ค. ๋ฐฉํ–ฅ์„ ๋งžํžˆ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์‹ค์ œ ํฌ์ง€์…˜์„ ์žก์•„ "
245
+ "์ˆ˜์ต๋ฅ ๋กœ ๊ฒจ๋ฃฌ๋‹ค. get_rules ๋กœ ์‹œ์ž‘ํ•˜๋ผ.",
246
+ "en": "This is a financial forecasting challenge. You do not call direction โ€” you take "
247
+ "real positions and compete on returns. Start with get_rules."}
 
 
 
 
 
 
 
app/main.py CHANGED
@@ -42,8 +42,8 @@ SESSION_KEY = os.environ.get("FINCHAL_SESSION_KEY") or secrets.token_hex(16)
42
 
43
  app = FastAPI(title="FINCHAL")
44
  S1 = S.SEASONS[1]
45
- # ๋กœ์ปฌ์—์„œ ํ™”๋ฉด์„ ํ™•์ธํ•  ๋•Œ๋งŒ ์“ด๋‹ค. ์‹œ์ฆŒ์ด ์˜ค๋Š˜ ์—ด๋ฆฌ๋ฉด ์ฑ„์ ํ•  ๊ตฌ๊ฐ„์ด ์—†์–ด
46
- # ์ฐจํŠธ์™€ ์ˆœ์œ„๊ฐ€ ๋น„์–ด ๋ณด์ด๋Š”๋ฐ, ๊ทธ๊ฒŒ ๊ฒฐํ•จ์ธ์ง€ ์ •์ƒ์ธ์ง€ ๊ตฌ๋ถ„์ด ์•ˆ ๋œ๋‹ค.
47
  if os.environ.get("FINCHAL_OPENS"):
48
  S1 = dict(S1, opens=os.environ["FINCHAL_OPENS"])
49
  ASSETS = S1["assets"]
@@ -154,7 +154,28 @@ def score_asset(asset):
154
  """๊ทธ ์ข…๋ชฉ ์ฐธ๊ฐ€์ž ์ „์›์„ ์ฑ„์ ํ•œ๋‹ค. ๊ธฐ์ค€๋ถ„ํฌ๋Š” ์‹œ์ฆŒ ์‹ค์ œ ๊ฒฝ๋กœ๋กœ ๋งŒ๋“ ๋‹ค."""
155
  px = season_px(asset)
156
  if len(px) < 2:
157
- return [] # ์‹œ์ฆŒ ์ฒซ๋‚ ์—” ๊ณ„์‚ฐํ•  ๊ตฌ๊ฐ„์ด ์—†๋‹ค
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  fee = SC.fee_of(asset)
159
  recs = L.positions(asset)
160
  rows = []
 
42
 
43
  app = FastAPI(title="FINCHAL")
44
  S1 = S.SEASONS[1]
45
+ # ๊ฐœ๋ฐœ ์ค‘ ํ™”๋ฉด ํ™•์ธ์šฉ. ์‹œ์ฆŒ ๊ฐœ๋ง‰์ผ์—๋Š” ์ฑ„์ ํ•  ๊ตฌ๊ฐ„์ด ์—†์–ด ์ฐจํŠธ์™€ ์ˆœ์œ„๊ฐ€ ๋น„๋ฏ€๋กœ,
46
+ # ๊ฐœ๋ง‰ ์ „์— ํ™”๋ฉด์„ ๊ฒ€์ฆํ•˜๋ ค๋ฉด ์‹œ์ž‘์ผ์„ ์•ž๋‹น๊ฒจ ๋„์šด๋‹ค.
47
  if os.environ.get("FINCHAL_OPENS"):
48
  S1 = dict(S1, opens=os.environ["FINCHAL_OPENS"])
49
  ASSETS = S1["assets"]
 
154
  """๊ทธ ์ข…๋ชฉ ์ฐธ๊ฐ€์ž ์ „์›์„ ์ฑ„์ ํ•œ๋‹ค. ๊ธฐ์ค€๋ถ„ํฌ๋Š” ์‹œ์ฆŒ ์‹ค์ œ ๊ฒฝ๋กœ๋กœ ๋งŒ๋“ ๋‹ค."""
155
  px = season_px(asset)
156
  if len(px) < 2:
157
+ # ๐Ÿ”ด ๊ฐœ๋ง‰์ผ์—๋Š” ์ฑ„์ ํ•  ๊ตฌ๊ฐ„์ด ์—†๋‹ค. ๊ทธ๋ ‡๋‹ค๊ณ  ๋นˆ ํ‘œ๋ฅผ ๋‚ด๋ณด๋‚ด๋ฉด
158
+ # ์ด๋ฏธ ์ ‘์ˆ˜ํ•œ ์ฐธ๊ฐ€์ž์—๊ฒŒ "์•„๋ฌด๋„ ์—†๋‹ค"๊ณ  ๋งํ•˜๋Š” ์…ˆ์ด๊ณ , ๋ฐฉ๋ฌธ์ž์—๊ฒŒ๋Š”
159
+ # ๊ณ ์žฅ์œผ๋กœ ๋ณด์ธ๋‹ค. ์ ‘์ˆ˜๋œ ์‚ฌ์‹ค์€ ๊ทธ๋Œ€๋กœ ๋ณด์—ฌ ์ฃผ๊ณ  ์ˆ˜์น˜๋งŒ ๋น„์›Œ ๋‘”๋‹ค.
160
+ users = {u["user"]: u for u in L.all_users()}
161
+ seen, out = set(), []
162
+ for r in L.positions(asset):
163
+ seen.add(r["u"])
164
+ for uid in seen:
165
+ u = users.get(uid, {})
166
+ last = L.latest(asset, uid)
167
+ out.append({"user": uid, "strategy": u.get("strategy", "-"),
168
+ "type": u.get("type", "agent"),
169
+ "joined": u.get("joined", ""),
170
+ "ret": None, "score": None, "luck_p95": None,
171
+ "turnover": None, "fee_paid": None, "valid": None,
172
+ "position": last["p"] if last else None,
173
+ "pending": True,
174
+ "n_sub": len(L.positions(asset, uid))})
175
+ out.sort(key=lambda x: x["joined"])
176
+ for i, r in enumerate(out, 1):
177
+ r["rank"] = i
178
+ return out
179
  fee = SC.fee_of(asset)
180
  recs = L.positions(asset)
181
  rows = []
app/static/index.html CHANGED
@@ -266,6 +266,7 @@ pre{background:#0f1729;color:#e6edf7;border-radius:11px;padding:14px 16px;overfl
266
  <div class="card" style="margin-top:16px">
267
  <h2><span id="t-rank">โ€”</span>
268
  <span class="faint" style="font-weight:400;font-size:13px" id="rk-sub"></span></h2>
 
269
  <div class="tw"><table>
270
  <thead><tr>
271
  <th class="rk">#</th><th id="th-who">โ€”</th><th id="th-type">โ€”</th>
@@ -342,6 +343,9 @@ var S = {
342
  agentP:"Claude CodeยทCodex ๋“ฑ์— MCP ์„œ๋ฒ„๋ฅผ ๋ถ™์ด๋ฉด ๋„๊ตฌ ๋„ค ๊ฐœ๊ฐ€ ์ƒ๊น๋‹ˆ๋‹ค. " +
343
  "์•„๋ž˜๋ฅผ ๊ทธ๋Œ€๋กœ ๋ถ™์—ฌ๋„ฃ์œผ์‹ญ์‹œ์˜ค.",
344
  creed:"์ด ๋Œ€ํšŒ๋Š” ๋‹ต์„ ๋ชจ์œผ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ฒฝ๊ณ„๋ฅผ ์žฝ๋‹ˆ๋‹ค.",
 
 
 
345
  tv:"์‹ค์‹œ๊ฐ„ ์‹œ์„ธ", tvSub:"TradingView ์ œ๊ณต ยท ์ฑ„์ ์— ์“ฐ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ์ข…๋ชฉ",
346
  ta:"๊ธฐ์ˆ ์  ์ง€ํ‘œ ์š”์•ฝ",
347
  taNote:"์ฐธ๊ณ ์šฉ์ž…๋‹ˆ๋‹ค. ์ด ์‹ ํ˜ธ๋ฅผ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ฅด๋Š” ๊ฒƒ์ด ์ด๊ธฐ๋Š” ๋ฐฉ๋ฒ•์ด๋ผ๋Š” ๊ทผ๊ฑฐ๋Š” ์—†์Šต๋‹ˆ๋‹ค โ€” " +
@@ -389,6 +393,9 @@ var S = {
389
  agentP:"Attach the MCP server to Claude Code, Codex or any agent and it gains four tools. " +
390
  "Paste the block below.",
391
  creed:"This contest does not collect answers. It measures the boundary.",
 
 
 
392
  tv:"Live market", tvSub:"By TradingView ยท the same instrument we score",
393
  ta:"Technical summary",
394
  taNote:"Shown for reference. There is no evidence that following these signals wins โ€” " +
@@ -758,11 +765,27 @@ async function drawRank(a){
758
  var tb = $("rank");
759
  if (!d.rows || !d.rows.length) {
760
  tb.innerHTML = '<tr><td colspan="8" class="load">' + s.noRows + "</td></tr>";
 
761
  return;
762
  }
 
763
  var ceil = R.luck_ceiling[a];
764
  var mine = ME.signed_in ? ME.user.name : null;
765
  tb.innerHTML = d.rows.map(function(r){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
766
  var vs = r.ret > r.luck_p95
767
  ? '<span class="star">' + s.over + "</span>"
768
  : '<span class="faint">' +
 
266
  <div class="card" style="margin-top:16px">
267
  <h2><span id="t-rank">โ€”</span>
268
  <span class="faint" style="font-weight:400;font-size:13px" id="rk-sub"></span></h2>
269
+ <div class="note" id="rk-note" style="margin:0 0 12px"></div>
270
  <div class="tw"><table>
271
  <thead><tr>
272
  <th class="rk">#</th><th id="th-who">โ€”</th><th id="th-type">โ€”</th>
 
343
  agentP:"Claude CodeยทCodex ๋“ฑ์— MCP ์„œ๋ฒ„๋ฅผ ๋ถ™์ด๋ฉด ๋„๊ตฌ ๋„ค ๊ฐœ๊ฐ€ ์ƒ๊น๋‹ˆ๋‹ค. " +
344
  "์•„๋ž˜๋ฅผ ๊ทธ๋Œ€๋กœ ๋ถ™์—ฌ๋„ฃ์œผ์‹ญ์‹œ์˜ค.",
345
  creed:"์ด ๋Œ€ํšŒ๋Š” ๋‹ต์„ ๋ชจ์œผ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ฒฝ๊ณ„๋ฅผ ์žฝ๋‹ˆ๋‹ค.",
346
+ pending:"์ง‘๊ณ„ ์ „",
347
+ pendingNote:"์‹œ์ฆŒ์ด ์˜ค๋Š˜ ์—ด๋ ธ์Šต๋‹ˆ๋‹ค. ์ ‘์ˆ˜๋œ ํฌ์ง€์…˜์€ ์•„๋ž˜์™€ ๊ฐ™๊ณ , ์ˆ˜์ต๋ฅ ์€ " +
348
+ "ํ•˜๋ฃจ์น˜ ๊ตฌ๊ฐ„์ด ์Œ“์ด๋Š” ๋Œ€๋กœ ๊ณ„์‚ฐ๋˜์–ด ์ˆœ์œ„๊ฐ€ ๋งค๊ฒจ์ง‘๋‹ˆ๋‹ค.",
349
  tv:"์‹ค์‹œ๊ฐ„ ์‹œ์„ธ", tvSub:"TradingView ์ œ๊ณต ยท ์ฑ„์ ์— ์“ฐ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ์ข…๋ชฉ",
350
  ta:"๊ธฐ์ˆ ์  ์ง€ํ‘œ ์š”์•ฝ",
351
  taNote:"์ฐธ๊ณ ์šฉ์ž…๋‹ˆ๋‹ค. ์ด ์‹ ํ˜ธ๋ฅผ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ฅด๋Š” ๊ฒƒ์ด ์ด๊ธฐ๋Š” ๋ฐฉ๋ฒ•์ด๋ผ๋Š” ๊ทผ๊ฑฐ๋Š” ์—†์Šต๋‹ˆ๋‹ค โ€” " +
 
393
  agentP:"Attach the MCP server to Claude Code, Codex or any agent and it gains four tools. " +
394
  "Paste the block below.",
395
  creed:"This contest does not collect answers. It measures the boundary.",
396
+ pending:"pending",
397
+ pendingNote:"The season opened today. Positions received so far are listed below; " +
398
+ "returns and ranks appear once a full day of data accumulates.",
399
  tv:"Live market", tvSub:"By TradingView ยท the same instrument we score",
400
  ta:"Technical summary",
401
  taNote:"Shown for reference. There is no evidence that following these signals wins โ€” " +
 
765
  var tb = $("rank");
766
  if (!d.rows || !d.rows.length) {
767
  tb.innerHTML = '<tr><td colspan="8" class="load">' + s.noRows + "</td></tr>";
768
+ $("rk-note").textContent = "";
769
  return;
770
  }
771
+ $("rk-note").textContent = d.rows[0] && d.rows[0].pending ? s.pendingNote : "";
772
  var ceil = R.luck_ceiling[a];
773
  var mine = ME.signed_in ? ME.user.name : null;
774
  tb.innerHTML = d.rows.map(function(r){
775
+ if (r.pending) {
776
+ /* ๊ฐœ๋ง‰์ผ โ€” ์ ‘์ˆ˜๋Š” ๋์ง€๋งŒ ์žด ๊ตฌ๊ฐ„์ด ์•„์ง ์—†๋‹ค. ๋นˆ์นธ์ด ์•„๋‹ˆ๋ผ ์ƒํƒœ๋ฅผ ๋ณด์—ฌ ์ค€๋‹ค. */
777
+ var pos = (r.position === null || r.position === undefined) ? "โ€”"
778
+ : (r.position >= 0 ? "+" : "") + r.position.toFixed(2);
779
+ return '<tr class="' + (r.user === mine ? "mine" : "") + '">' +
780
+ '<td class="rk">ยท</td>' +
781
+ '<td class="nm"><b>' + r.user + "</b><small>" + r.strategy + "</small></td>" +
782
+ '<td><span class="pill ' + (r.type === "anchor" ? "base" : "") + '">' +
783
+ (R.types[r.type] || r.type) + "</span></td>" +
784
+ '<td class="num mono faint">' + s.pending + "</td>" +
785
+ '<td class="num mono ' + cls(r.position || 0) + '">' + pos + "</td>" +
786
+ '<td class="num faint">โ€”</td><td class="num faint">โ€”</td>' +
787
+ '<td class="num faint">' + (r.joined || "").slice(5, 10) + "</td></tr>";
788
+ }
789
  var vs = r.ret > r.luck_p95
790
  ? '<span class="star">' + s.over + "</span>"
791
  : '<span class="faint">' +
reference.py CHANGED
@@ -1,124 +1,124 @@
1
- # -*- coding: utf-8 -*-
2
- """๊ธฐ์ค€๋ถ„ํฌ โ€” '์‹ค๋ ฅ 0์ด์—ˆ๋‹ค๋ฉด ์–ด๋””๊นŒ์ง€ ๊ฐ”์„๊นŒ'๋ฅผ ๊ณ„์‚ฐํ•œ๋‹ค.
3
-
4
- ๋‘ ๊ฐ€์ง€๋ฅผ ๊ตฌ๋ถ„ํ•œ๋‹ค.
5
-
6
- ๊ฐœ๋ง‰ ์ „ ์ถ”์ •์น˜ (announce)
7
- ์ฐธ๊ฐ€์ž์—๊ฒŒ "์ด ์ข…๋ชฉ์€ ์šด์œผ๋กœ ์ด๋งŒํผ๊นŒ์ง€ ๋‚˜์˜จ๋‹ค"๊ณ  ๋ฏธ๋ฆฌ ์•Œ๋ ค์ฃผ๋Š” ๊ฐ’.
8
- ๊ณผ๊ฑฐ 5๋…„์„ ๋ธ”๋ก ๋ถ€ํŠธ์ŠคํŠธ๋žฉํ•ด์„œ ๋‚ธ๋‹ค. ํ™”๋ฉด์˜ '์šด์˜ ํ•œ๊ณ„์„ '์— ์“ด๋‹ค.
9
-
10
- ์ฑ„์ ์šฉ ๊ธฐ์ค€ (live)
11
- ์‹ค์ œ ์ˆœ์œ„๋ฅผ ์ •ํ•˜๋Š” ๊ฐ’. ์‹œ์ฆŒ์ด ์ง„ํ–‰๋œ ๋งŒํผ์˜ **์‹ค์ œ ๊ฒฝ๋กœ** ์œ„์—์„œ
12
- ์‹ค๋ ฅ 0 ์ฐธ๊ฐ€์ž๋ฅผ ๋Œ๋ ค ๋งŒ๋“ ๋‹ค. ๋งค์ผ ๋‹ค์‹œ ๊ณ„์‚ฐํ•œ๋‹ค.
13
-
14
- ์™œ ๋‚˜๋ˆ„๋Š”๊ฐ€
15
- ๊ธฐ์ค€์„ ๊ณผ๊ฑฐ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์œผ๋กœ ๊ณ ์ •ํ•˜๋ฉด, ์‹œ์ฆŒ ์ค‘ ๊ทธ ์ข…๋ชฉ์ด ํฌ๊ฒŒ ์˜ค๋ฅผ ๋•Œ
16
- ์ฐธ๊ฐ€์ž ์ „์›์ด ๋ถ€ํ’€๋ ค์ง„๋‹ค. ์‹ค์ธก์—์„œ GOLD ์šฐ์Šน๋ฅ  99.6%๊ฐ€ ๋‚˜์™”๋‹ค.
17
- ์˜ฌ๋ฐ”๋ฅธ ๋ฐ˜์‚ฌ์‹ค์€ '์‹œ์žฅ์ด ์‹ค์ œ๋กœ ๊ทธ๋ ‡๊ฒŒ ์›€์ง์˜€์„ ๋•Œ ์šด์œผ๋กœ ๋‚ผ ์ˆ˜ ์žˆ์—ˆ๋˜ ์„ฑ์ '์ด๋‹ค.
18
-
19
- ๐Ÿ”‘ ๊ฐœ๋ง‰ ์ „์— ๊ณ ์ •ยท๊ณต๊ฐœํ•˜๋Š” ๊ฒƒ์€ ์ˆซ์ž๊ฐ€ ์•„๋‹ˆ๋ผ **๋ฐฉ๋ฒ•**์ด๋‹ค.
20
- ์ˆซ์ž๋ฅผ ๊ณ ์ •ํ•˜๋ฉด ์‹œ์žฅ์ด ์˜ˆ์ƒ๊ณผ ๋‹ค๋ฅด๊ฒŒ ์›€์ง์˜€์„ ๋•Œ ์ฑ„์ ์ด ํ†ต์งธ๋กœ ์–ด๊ธ‹๋‚œ๋‹ค.
21
-
22
- ๐Ÿ”‘ ๊ธฐ์ค€ ์ฐธ๊ฐ€์ž๋„ ์ฐธ๊ฐ€์ž์™€ ๋˜‘๊ฐ™์ด ์ˆ˜์ˆ˜๋ฃŒ๋ฅผ ๋‚ธ๋‹ค.
23
- ์•ˆ ๋ฌผ๋ฆฌ๋ฉด ์ฐธ๊ฐ€์ž๋งŒ ์†ํ•ด๋ฅผ ๋ณด๊ณ  ์‹œ์ž‘ํ•œ๋‹ค. BTC์—์„œ ๊ทธ ์ฐจ์ด๊ฐ€ 77.2%p์˜€๋‹ค.
24
- """
25
- import os, json, warnings
26
- import numpy as np, pandas as pd
27
- warnings.filterwarnings("ignore")
28
-
29
- ROOT = os.path.dirname(os.path.abspath(__file__))
30
- BLOCK = 5
31
- N_LIVE = 4000
32
- N_ANNOUNCE = 20000
33
-
34
-
35
- def _sim(returns, n, expo, fee, seed, block=None):
36
- """์‹ค๋ ฅ 0 ์ฐธ๊ฐ€์ž n๋ช…์˜ ์ตœ์ข… ์ˆ˜์ต๋ฅ . expo ๋งŒํผ์˜ ์‹œ๊ฐ„์„ ํฌ์ง€์…˜์— ๋…ธ์ถœํ•œ๋‹ค."""
37
- g = np.random.default_rng(seed)
38
- r = np.asarray(returns, dtype=float)
39
- L = len(r)
40
- out = np.empty(n)
41
- for i in range(n):
42
- if block: # ๋ถ€ํŠธ์ŠคํŠธ๋žฉ (๊ฐœ๋ง‰ ์ „ ์ถ”์ •)
43
- nb = int(np.ceil(L / block))
44
- st = g.integers(0, max(len(r) - block, 1), size=nb)
45
- path = np.concatenate([r[j:j+block] for j in st])[:L]
46
- else: # ์‹ค์ œ ๊ฒฝ๋กœ (์ฑ„์ )
47
- path = r
48
- act = g.random(L) < expo
49
- pos = np.where(act, g.choice([-1.0, 1.0], size=L), 0.0)
50
- cost = np.abs(np.diff(np.r_[0.0, pos])) * fee
51
- out[i] = np.prod(1 + np.r_[0.0, pos[:-1]] * path - cost) - 1
52
- return np.sort(out)
53
-
54
-
55
- def live(prices, fee, expo=2/3, n=N_LIVE, seed=None):
56
- """์ฑ„์ ์šฉ โ€” ์‹œ์ฆŒ ๊ฒฝ๊ณผ๋ถ„์˜ ์‹ค์ œ ๊ฒฝ๋กœ ์œ„์—์„œ ๋งŒ๋“ ๋‹ค. ๋งค์ผ ๋‹ค์‹œ ๋ถ€๋ฅธ๋‹ค.
57
-
58
- seed ๋ฅผ ๋‚ ์งœ๋กœ ์ฃผ๋ฉด ๊ฐ™์€ ๋‚  ๊ฐ™์€ ๊ฐ’์ด ๋‚˜์™€ ์žฌํ˜„์ด ๋œ๋‹ค.
59
- """
60
- r = pd.Series(prices, dtype=float).sort_index().pct_change().fillna(0.0).values
61
- s = seed if seed is not None else int(pd.Timestamp.utcnow().strftime("%Y%m%d"))
62
- return _sim(r, n, expo, fee, s)
63
-
64
-
65
- def announce(returns, fee, days, expo=2/3, n=N_ANNOUNCE, seed=20260824):
66
- """๊ฐœ๋ง‰ ์ „ ์ถ”์ • โ€” ๊ณผ๊ฑฐ๋ฅผ ๋ธ”๋ก ๋ถ€ํŠธ์ŠคํŠธ๋žฉํ•ด์„œ '์ด๋งŒํผ๊นŒ์ง€ ๋‚˜์˜จ๋‹ค'๋ฅผ ๋‚ธ๋‹ค."""
67
- r = np.asarray(returns, dtype=float)
68
- # ์‹œ์ฆŒ ๊ธธ์ด๋งŒํผ๋งŒ ์“ฐ๋„๋ก ์ž˜๋ผ ๋ถ™์ธ๋‹ค
69
- reps = int(np.ceil(days / len(r))) + 1
70
- pool = np.tile(r, reps)[:max(len(r), days * 3)]
71
- return _sim(pool[:days] if len(pool) >= days else pool, n, expo, fee, seed, block=BLOCK)
72
-
73
-
74
- def pct_of(ret, refv):
75
- import bisect
76
- return bisect.bisect_left(refv, ret) / len(refv)
77
-
78
-
79
- def score_of(ret, refv):
80
- import math
81
- p = min(pct_of(ret, refv), 1 - 1e-6)
82
- return -math.log10(max(1.0 - p, 1e-6))
83
-
84
-
85
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ๊ฐœ๋ง‰ ๊ณต์ง€๊ฐ’ ์ƒ์„ฑ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
86
- if __name__ == "__main__":
87
- import seasons as S
88
- import scoring as SC
89
- import yfinance as yf
90
-
91
- s1 = S.SEASONS[1]
92
- days = (pd.Timestamp(s1["closes"]) - pd.Timestamp(s1["opens"])).days
93
- print("์‹œ์ฆŒ 1 ๊ฐœ๋ง‰ ๊ณต์ง€๊ฐ’ โ€” %d์ผ ยท ์ˆ˜์ˆ˜๋ฃŒ ๋ฐ˜์˜\n" % days)
94
- print(" %-6s %-8s %8s %11s %11s %11s"
95
- % ("์ข…๋ชฉ", "๋ฐ์ดํ„ฐ", "์ˆ˜์ˆ˜๋ฃŒ", "์šด ์ค‘์•™", "์šด ์ƒ์œ„5%", "์šด ์ƒ์œ„1%"))
96
- out = {}
97
- for code, a in s1["assets"].items():
98
- src = a["src"]
99
- if code == "BTC":
100
- p = os.path.join(os.path.dirname(ROOT), "btc-oracle", "data", "hourly.parquet")
101
- H = pd.read_parquet(p).sort_index()
102
- if "close" not in H.columns:
103
- H = H.rename(columns={"c": "close"})
104
- px = H.close.groupby(H.index.normalize()).last()
105
- else:
106
- d = yf.download(src, period="5y", interval="1d",
107
- progress=False, auto_adjust=True)["Close"]
108
- px = (d.iloc[:, 0] if isinstance(d, pd.DataFrame) else d).dropna()
109
- r = px.pct_change().dropna().values
110
- fee = SC.fee_of(code)
111
- c = announce(r, fee, days)
112
- out[code] = {"p50": float(np.median(c)), "p95": float(np.percentile(c, 95)),
113
- "p99": float(np.percentile(c, 99)), "fee": fee, "days": days}
114
- print(" %-6s %-8s %7.2f%% %10.1f%% %10.1f%% %10.1f%%"
115
- % (code, src, fee * 100, np.median(c) * 100,
116
- np.percentile(c, 95) * 100, np.percentile(c, 99) * 100))
117
- json.dump({"season": 1, "opens": s1["opens"], "closes": s1["closes"], "days": days,
118
- "method": "๋ธ”๋ก๋ถ€ํŠธ์ŠคํŠธ๋žฉ 5์ผ ยท ๋ฌด์ž‘์œ„ ํฌ์ง€์…˜ ยท ๋ ˆ๋ฒ„๋ฆฌ์ง€1 ยท ์ˆ˜์ˆ˜๋ฃŒ ๋ฐ˜์˜",
119
- "note": "๊ฐœ๋ง‰ ๊ณต์ง€์šฉ ์ถ”์ •์น˜. ์‹ค์ œ ์ฑ„์ ์€ ์‹œ์ฆŒ ์‹ค์ œ ๊ฒฝ๋กœ๋กœ ๋งค์ผ ์žฌ๊ณ„์‚ฐํ•œ๋‹ค.",
120
- "ceiling": out},
121
- open(os.path.join(ROOT, "announce_ceiling.json"), "w"),
122
- ensure_ascii=False, indent=1)
123
- print("\n ํ™”๋ฉด์˜ '์šด์˜ ํ•œ๊ณ„์„ '์—๋Š” ์ƒ์œ„5%๋ฅผ ์“ด๋‹ค.")
124
- print(" ์ €์žฅ: finchal/announce_ceiling.json")
 
1
+ # -*- coding: utf-8 -*-
2
+ """๊ธฐ์ค€๋ถ„ํฌ โ€” '์‹ค๋ ฅ 0์ด์—ˆ๋‹ค๋ฉด ์–ด๋””๊นŒ์ง€ ๊ฐ”์„๊นŒ'๋ฅผ ๊ณ„์‚ฐํ•œ๋‹ค.
3
+
4
+ ๋‘ ๊ฐ€์ง€๋ฅผ ๊ตฌ๋ถ„ํ•œ๋‹ค.
5
+
6
+ ๊ฐœ๋ง‰ ์ „ ์ถ”์ •์น˜ (announce)
7
+ ์ฐธ๊ฐ€์ž์—๊ฒŒ "์ด ์ข…๋ชฉ์€ ์šด์œผ๋กœ ์ด๋งŒํผ๊นŒ์ง€ ๋‚˜์˜จ๋‹ค"๊ณ  ๋ฏธ๋ฆฌ ์•Œ๋ ค์ฃผ๋Š” ๊ฐ’.
8
+ ๊ณผ๊ฑฐ 5๋…„์„ ๋ธ”๋ก ๋ถ€ํŠธ์ŠคํŠธ๋žฉํ•ด์„œ ๋‚ธ๋‹ค. ํ™”๋ฉด์˜ '์šด์˜ ํ•œ๊ณ„์„ '์— ์“ด๋‹ค.
9
+
10
+ ์ฑ„์ ์šฉ ๊ธฐ์ค€ (live)
11
+ ์‹ค์ œ ์ˆœ์œ„๋ฅผ ์ •ํ•˜๋Š” ๊ฐ’. ์‹œ์ฆŒ์ด ์ง„ํ–‰๋œ ๋งŒํผ์˜ **์‹ค์ œ ๊ฒฝ๋กœ** ์œ„์—์„œ
12
+ ์‹ค๋ ฅ 0 ์ฐธ๊ฐ€์ž๋ฅผ ๋Œ๋ ค ๋งŒ๋“ ๋‹ค. ๋งค์ผ ๋‹ค์‹œ ๊ณ„์‚ฐํ•œ๋‹ค.
13
+
14
+ ์™œ ๋‚˜๋ˆ„๋Š”๊ฐ€
15
+ ๊ธฐ์ค€์„ ๊ณผ๊ฑฐ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์œผ๋กœ ๊ณ ์ •ํ•˜๋ฉด, ์‹œ์ฆŒ ์ค‘ ๊ทธ ์ข…๋ชฉ์ด ํฌ๊ฒŒ ์˜ค๋ฅผ ๋•Œ ์ฐธ๊ฐ€์ž
16
+ ์ „์›์˜ ์„ฑ์ ์ด ํ•จ๊ป˜ ๋ถ€ํ’€๋ ค์ ธ ์ˆœ์œ„๊ฐ€ ์‹œ์žฅ ๋ฐฉํ–ฅ์„ ๋”ฐ๋ผ๊ฐ„๋‹ค. ์˜ฌ๋ฐ”๋ฅธ ๋ฐ˜์‚ฌ์‹ค์€
17
+ '์‹œ์žฅ์ด ์‹ค์ œ๋กœ ๊ทธ๋ ‡๊ฒŒ ์›€์ง์˜€์„ ๋•Œ ์šด์œผ๋กœ ๋‚ผ ์ˆ˜ ์žˆ์—ˆ๋˜ ์„ฑ์ '์ด๋‹ค.
18
+
19
+ ๐Ÿ”‘ ๊ฐœ๋ง‰ ์ „์— ๊ณ ์ •ยท๊ณต๊ฐœํ•˜๋Š” ๊ฒƒ์€ ์ˆซ์ž๊ฐ€ ์•„๋‹ˆ๋ผ **๋ฐฉ๋ฒ•**์ด๋‹ค.
20
+ ์ˆซ์ž๋ฅผ ๊ณ ์ •ํ•˜๋ฉด ์‹œ์žฅ์ด ์˜ˆ์ƒ๊ณผ ๋‹ค๋ฅด๊ฒŒ ์›€์ง์˜€์„ ๋•Œ ์ฑ„์ ์ด ํ†ต์งธ๋กœ ์–ด๊ธ‹๋‚œ๋‹ค.
21
+
22
+ ๐Ÿ”‘ ๊ธฐ์ค€ ์ฐธ๊ฐ€์ž๋„ ์ฐธ๊ฐ€์ž์™€ ๋˜‘๊ฐ™์ด ์ˆ˜์ˆ˜๋ฃŒ๋ฅผ ๋‚ธ๋‹ค. ์•ˆ ๋ฌผ๋ฆฌ๋ฉด ์ฐธ๊ฐ€์ž๋งŒ ๋น„์šฉ์„
23
+ ์ง€๊ณ  ์‹œ์ž‘ํ•œ๋‹ค. ์ˆ˜์ˆ˜๋ฃŒ๊ฐ€ ๋†’์€ ์ข…๋ชฉ์ผ์ˆ˜๋ก ๊ทธ ์ฐจ์ด๊ฐ€ ์ปค์ง„๋‹ค.
24
+ """
25
+ import os, json, warnings
26
+ import numpy as np, pandas as pd
27
+ warnings.filterwarnings("ignore")
28
+
29
+ ROOT = os.path.dirname(os.path.abspath(__file__))
30
+ BLOCK = 5
31
+ N_LIVE = 4000
32
+ N_ANNOUNCE = 20000
33
+
34
+
35
+ def _sim(returns, n, expo, fee, seed, block=None):
36
+ """์‹ค๋ ฅ 0 ์ฐธ๊ฐ€์ž n๋ช…์˜ ์ตœ์ข… ์ˆ˜์ต๋ฅ . expo ๋งŒํผ์˜ ์‹œ๊ฐ„์„ ํฌ์ง€์…˜์— ๋…ธ์ถœํ•œ๋‹ค."""
37
+ g = np.random.default_rng(seed)
38
+ r = np.asarray(returns, dtype=float)
39
+ L = len(r)
40
+ out = np.empty(n)
41
+ for i in range(n):
42
+ if block: # ๋ถ€ํŠธ์ŠคํŠธ๋žฉ (๊ฐœ๋ง‰ ์ „ ์ถ”์ •)
43
+ nb = int(np.ceil(L / block))
44
+ st = g.integers(0, max(len(r) - block, 1), size=nb)
45
+ path = np.concatenate([r[j:j+block] for j in st])[:L]
46
+ else: # ์‹ค์ œ ๊ฒฝ๋กœ (์ฑ„์ )
47
+ path = r
48
+ act = g.random(L) < expo
49
+ pos = np.where(act, g.choice([-1.0, 1.0], size=L), 0.0)
50
+ cost = np.abs(np.diff(np.r_[0.0, pos])) * fee
51
+ out[i] = np.prod(1 + np.r_[0.0, pos[:-1]] * path - cost) - 1
52
+ return np.sort(out)
53
+
54
+
55
+ def live(prices, fee, expo=2/3, n=N_LIVE, seed=None):
56
+ """์ฑ„์ ์šฉ โ€” ์‹œ์ฆŒ ๊ฒฝ๊ณผ๋ถ„์˜ ์‹ค์ œ ๊ฒฝ๋กœ ์œ„์—์„œ ๋งŒ๋“ ๋‹ค. ๋งค์ผ ๋‹ค์‹œ ๋ถ€๋ฅธ๋‹ค.
57
+
58
+ seed ๋ฅผ ๋‚ ์งœ๋กœ ์ฃผ๋ฉด ๊ฐ™์€ ๋‚  ๊ฐ™์€ ๊ฐ’์ด ๋‚˜์™€ ์žฌํ˜„์ด ๋œ๋‹ค.
59
+ """
60
+ r = pd.Series(prices, dtype=float).sort_index().pct_change().fillna(0.0).values
61
+ s = seed if seed is not None else int(pd.Timestamp.utcnow().strftime("%Y%m%d"))
62
+ return _sim(r, n, expo, fee, s)
63
+
64
+
65
+ def announce(returns, fee, days, expo=2/3, n=N_ANNOUNCE, seed=20260824):
66
+ """๊ฐœ๋ง‰ ์ „ ์ถ”์ • โ€” ๊ณผ๊ฑฐ๋ฅผ ๋ธ”๋ก ๋ถ€ํŠธ์ŠคํŠธ๋žฉํ•ด์„œ '์ด๋งŒํผ๊นŒ์ง€ ๋‚˜์˜จ๋‹ค'๋ฅผ ๋‚ธ๋‹ค."""
67
+ r = np.asarray(returns, dtype=float)
68
+ # ์‹œ์ฆŒ ๊ธธ์ด๋งŒํผ๋งŒ ์“ฐ๋„๋ก ์ž˜๋ผ ๋ถ™์ธ๋‹ค
69
+ reps = int(np.ceil(days / len(r))) + 1
70
+ pool = np.tile(r, reps)[:max(len(r), days * 3)]
71
+ return _sim(pool[:days] if len(pool) >= days else pool, n, expo, fee, seed, block=BLOCK)
72
+
73
+
74
+ def pct_of(ret, refv):
75
+ import bisect
76
+ return bisect.bisect_left(refv, ret) / len(refv)
77
+
78
+
79
+ def score_of(ret, refv):
80
+ import math
81
+ p = min(pct_of(ret, refv), 1 - 1e-6)
82
+ return -math.log10(max(1.0 - p, 1e-6))
83
+
84
+
85
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ๊ฐœ๋ง‰ ๊ณต์ง€๊ฐ’ ์ƒ์„ฑ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
86
+ if __name__ == "__main__":
87
+ import seasons as S
88
+ import scoring as SC
89
+ import yfinance as yf
90
+
91
+ s1 = S.SEASONS[1]
92
+ days = (pd.Timestamp(s1["closes"]) - pd.Timestamp(s1["opens"])).days
93
+ print("์‹œ์ฆŒ 1 ๊ฐœ๋ง‰ ๊ณต์ง€๊ฐ’ โ€” %d์ผ ยท ์ˆ˜์ˆ˜๋ฃŒ ๋ฐ˜์˜\n" % days)
94
+ print(" %-6s %-8s %8s %11s %11s %11s"
95
+ % ("์ข…๋ชฉ", "๋ฐ์ดํ„ฐ", "์ˆ˜์ˆ˜๋ฃŒ", "์šด ์ค‘์•™", "์šด ์ƒ์œ„5%", "์šด ์ƒ์œ„1%"))
96
+ out = {}
97
+ for code, a in s1["assets"].items():
98
+ src = a["src"]
99
+ if code == "BTC":
100
+ p = os.path.join(os.path.dirname(ROOT), "btc-oracle", "data", "hourly.parquet")
101
+ H = pd.read_parquet(p).sort_index()
102
+ if "close" not in H.columns:
103
+ H = H.rename(columns={"c": "close"})
104
+ px = H.close.groupby(H.index.normalize()).last()
105
+ else:
106
+ d = yf.download(src, period="5y", interval="1d",
107
+ progress=False, auto_adjust=True)["Close"]
108
+ px = (d.iloc[:, 0] if isinstance(d, pd.DataFrame) else d).dropna()
109
+ r = px.pct_change().dropna().values
110
+ fee = SC.fee_of(code)
111
+ c = announce(r, fee, days)
112
+ out[code] = {"p50": float(np.median(c)), "p95": float(np.percentile(c, 95)),
113
+ "p99": float(np.percentile(c, 99)), "fee": fee, "days": days}
114
+ print(" %-6s %-8s %7.2f%% %10.1f%% %10.1f%% %10.1f%%"
115
+ % (code, src, fee * 100, np.median(c) * 100,
116
+ np.percentile(c, 95) * 100, np.percentile(c, 99) * 100))
117
+ json.dump({"season": 1, "opens": s1["opens"], "closes": s1["closes"], "days": days,
118
+ "method": "๋ธ”๋ก๋ถ€ํŠธ์ŠคํŠธ๋žฉ 5์ผ ยท ๋ฌด์ž‘์œ„ ํฌ์ง€์…˜ ยท ๋ ˆ๋ฒ„๋ฆฌ์ง€1 ยท ์ˆ˜์ˆ˜๋ฃŒ ๋ฐ˜์˜",
119
+ "note": "๊ฐœ๋ง‰ ๊ณต์ง€์šฉ ์ถ”์ •์น˜. ์‹ค์ œ ์ฑ„์ ์€ ์‹œ์ฆŒ ์‹ค์ œ ๊ฒฝ๋กœ๋กœ ๋งค์ผ ์žฌ๊ณ„์‚ฐํ•œ๋‹ค.",
120
+ "ceiling": out},
121
+ open(os.path.join(ROOT, "announce_ceiling.json"), "w"),
122
+ ensure_ascii=False, indent=1)
123
+ print("\n ํ™”๋ฉด์˜ '์šด์˜ ํ•œ๊ณ„์„ '์—๋Š” ์ƒ์œ„5%๋ฅผ ์“ด๋‹ค.")
124
+ print(" ์ €์žฅ: finchal/announce_ceiling.json")
scoring.py CHANGED
@@ -8,8 +8,8 @@
8
  ๊ฑฐ๋ž˜๋น„์šฉ = ํฌ์ง€์…˜ ๋ณ€๊ฒฝ๋ถ„์— ์™•๋ณต 0.02% (๋ฏธ๊ตญ์ฃผ์‹ ๋ฌด๋ฃŒ + ์Šคํ”„๋ ˆ๋“œ ์ถ”์ •)
9
  ์ˆœ์œ„ ์ ์ˆ˜ = -log10(1 - ๋ฐฑ๋ถ„์œ„) ๋ฐฑ๋ถ„์œ„๋Š” ๊ทธ ์ข…๋ชฉ์˜ '์‹ค๋ ฅ0 ๋ถ„ํฌ' ๊ธฐ์ค€
10
 
11
- ๐Ÿ”ด ์ฑ„์ ๊ธฐ๋ฅผ ๋ชจ๋ธ๋ณด๋‹ค ๋จผ์ € ๊ฒ€์ฆํ•œ๋‹ค. ์ด ํŒŒ์ผ์„ ๋ฐ”๊พธ๋ฉด selftest()๋ฅผ ๋ฐ˜๋“œ์‹œ ๋‹ค์‹œ ๋Œ๋ฆฐ๋‹ค.
12
- ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ๋งŒ ์ฑ„์ ยท์‹œ๋ฎฌ ๊ฒฐํ•จ์„ 8๊ฑด ๊ฒช์—ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„ '๊ทธ๋Ÿด๋“ฏํ•˜๊ฒŒ ํ‹€๋ฆฐ' ๊ฐ’์ด์—ˆ๋‹ค.
13
  """
14
  import os, json, math, bisect
15
  import numpy as np
@@ -89,12 +89,11 @@ def percentile(ret, refv):
89
  def score(ret, refv):
90
  """์šด์œผ๋กœ ์ด ์„ฑ์ ์ด ๋‚˜์˜ฌ ํ™•๋ฅ ์˜ ์—ญ์ˆ˜ ์ง€์ˆ˜. 2.0์ด๋ฉด 1/100, 3.0์ด๋ฉด 1/1,000.
91
 
92
- ๐Ÿ”‘ ์ˆœ์œ„๋Š” **์ข…๋ชฉ ์•ˆ์—์„œ๋งŒ** ๋งค๊ธด๋‹ค. ์ƒ๊ธˆ์ด ์ข…๋ชฉ๋ณ„์ด๋ผ ์ข…๋ชฉ ๊ฐ„ ๋น„๊ต๋ฅผ ํ•˜์ง€ ์•Š๋Š”๋‹ค.
93
- ์ข…๋ชฉ ๊ฐ„ ๋น„๊ต๋Š” ์—ฌ์„ฏ ๋ฒˆ ์‹œ๋„ํ•ด๋„ ํŽธ์ค‘์ด ์žกํžˆ์ง€ ์•Š์•˜๋‹ค
94
- (4.7 -> 99.6 -> 3.7 -> 6.0 -> 4.5๋ฐฐ). ๋ถ„ํฌ ๊ผฌ๋ฆฌ ๋ชจ์–‘์ด ์ข…๋ชฉ๋งˆ๋‹ค ๋‹ค๋ฅด๊ณ ,
95
- ์šฐ์Šน์ž๋Š” ์ตœ๋Œ“๊ฐ’์ด๋ผ ๊ผฌ๋ฆฌ๊ฐ€ ๊ฒฐ์ •ํ•œ๋‹ค. ์ข…๋ชฉ๋ณ„ ์‹œ์ƒ์ด ๊ทธ ๋น„๊ต๋ฅผ ์—†์•ค๋‹ค.
96
 
97
- ์ข…๋ชฉ ์•ˆ์—์„œ๋Š” ์ž˜ ๊ต์ •๋œ๋‹ค โ€” ์‹ค๋ ฅ0 ์ฐธ๊ฐ€์ž์˜ ๋ฐฑ๋ถ„์œ„ ์ค‘์•™์ด 0.47~0.54์˜€๋‹ค.
98
  """
99
  p = min(percentile(ret, refv), 1 - 1e-6)
100
  return -math.log10(max(1.0 - p, 1e-6))
 
8
  ๊ฑฐ๋ž˜๋น„์šฉ = ํฌ์ง€์…˜ ๋ณ€๊ฒฝ๋ถ„์— ์™•๋ณต 0.02% (๋ฏธ๊ตญ์ฃผ์‹ ๋ฌด๋ฃŒ + ์Šคํ”„๋ ˆ๋“œ ์ถ”์ •)
9
  ์ˆœ์œ„ ์ ์ˆ˜ = -log10(1 - ๋ฐฑ๋ถ„์œ„) ๋ฐฑ๋ถ„์œ„๋Š” ๊ทธ ์ข…๋ชฉ์˜ '์‹ค๋ ฅ0 ๋ถ„ํฌ' ๊ธฐ์ค€
10
 
11
+ ์ด ํŒŒ์ผ์„ ๋ฐ”๊พธ๋ฉด selftest() ๋ฅผ ๋ฐ˜๋“œ์‹œ ๋‹ค์‹œ ๋Œ๋ฆฐ๋‹ค. ์ฑ„์  ์ฝ”๋“œ์˜ ์˜ค๋ฅ˜๋Š” ์˜ˆ์™ธ๋กœ
12
+ ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๊ณ  ๊ทธ๋Ÿด๋“ฏํ•œ ์ˆซ์ž๋กœ ๋‚˜์˜ค๋ฏ€๋กœ, ๋‹ซํžŒ ํ˜•ํƒœ๋กœ ๋‹ต์„ ์•„๋Š” ๊ฒฝ์šฐ๋“ค๋กœ๋งŒ ๊ฒ€์ฆํ•œ๋‹ค.
13
  """
14
  import os, json, math, bisect
15
  import numpy as np
 
89
  def score(ret, refv):
90
  """์šด์œผ๋กœ ์ด ์„ฑ์ ์ด ๋‚˜์˜ฌ ํ™•๋ฅ ์˜ ์—ญ์ˆ˜ ์ง€์ˆ˜. 2.0์ด๋ฉด 1/100, 3.0์ด๋ฉด 1/1,000.
91
 
92
+ ๐Ÿ”‘ ์ˆœ์œ„๋Š” ์ข…๋ชฉ ์•ˆ์—์„œ๋งŒ ๋งค๊ธด๋‹ค. ์ˆ˜์ต๋ฅ  ๋ถ„ํฌ์˜ ๊ผฌ๋ฆฌ ๋ชจ์–‘์ด ์ข…๋ชฉ๋งˆ๋‹ค ๋‹ฌ๋ผ
93
+ ์ข…๋ชฉ ๊ฐ„ ์ง์ ‘ ๋น„๊ต๋Š” ์–ด๋А ํ†ต๊ณ„๋Ÿ‰์„ ๋งž์ถ”์–ด๋„ ํŽธ์ค‘์ด ๋‚จ๋Š”๋‹ค. ์šฐ์Šน์ž๋Š”
94
+ ์ตœ๋Œ“๊ฐ’์ด๊ณ  ์ตœ๋Œ“๊ฐ’์€ ๊ผฌ๋ฆฌ๊ฐ€ ์ •ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ข…๋ชฉ๋ณ„ ์‹œ์ƒ์ด ๊ทธ ๋น„๊ต๋ฅผ ์—†์•ค๋‹ค.
 
95
 
96
+ ์ข…๋ชฉ ์•ˆ์—์„œ๋Š” ์ž˜ ๊ต์ •๋œ๋‹ค โ€” ์‹ค๋ ฅ 0 ์ฐธ๊ฐ€์ž์˜ ๋ฐฑ๋ถ„์œ„ ์ค‘์•™์ด 0.47~0.54๋‹ค.
97
  """
98
  p = min(percentile(ret, refv), 1 - 1e-6)
99
  return -math.log10(max(1.0 - p, 1e-6))