Spaces:
Running
Running
show received entries before scoring begins
Browse files- app/fetch.py +3 -4
- app/i18n.py +247 -254
- app/main.py +24 -3
- app/static/index.html +23 -0
- reference.py +124 -124
- scoring.py +6 -7
app/fetch.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
"""์์ธ ์์ง โ ์์ค ํ๋์ ๊ธฐ๋์ง ์๋๋ค.
|
| 3 |
|
| 4 |
-
๐ด
|
| 5 |
-
|
| 6 |
-
|
| 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 |
-
#
|
| 128 |
-
#
|
| 129 |
-
#
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
"
|
| 140 |
-
"
|
| 141 |
-
"
|
| 142 |
-
"
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
"
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
"
|
| 153 |
-
"
|
| 154 |
-
"
|
| 155 |
-
"122
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
"
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
"
|
| 168 |
-
"
|
| 169 |
-
"
|
| 170 |
-
"
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
"en": "
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
({"ko": "
|
| 192 |
-
{"ko": "
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
"
|
| 215 |
-
"
|
| 216 |
-
"
|
| 217 |
-
}
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
"
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
"
|
| 230 |
-
"
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
"
|
| 238 |
-
"
|
| 239 |
-
"ko": "
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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")
|
|
|
|
| 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 |
-
|
| 12 |
-
|
| 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 |
-
|
| 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))
|