๐ŸŒ

golden-banana โ€” Momentum Golden Cross

ํ™•๋ฅ  85~97% ๊ตฌ๊ฐ„์˜ ๊ณ ํ™•๋ฅ  outcome์„, ์ž์ฒด ์ถ•์ ํ•œ 5๋ถ„ ์Šค๋ƒ…์ƒท ๊ธฐ๋ฐ˜ momentum์ด ์ƒ์Šน ์ค‘์ผ ๋•Œ๋งŒ ๋งค์ˆ˜ํ•˜๋Š” golden cross ์ „๋žต.

์œ ํ˜• ๋ชจ๋ฉ˜ํ…€ ํŽธ์Šน์ƒํƒœ ์šด์˜ ์ค‘ํ•ต์‹ฌ ํŽธํ–ฅ underreaction (์ •๋ณด ํ™•์‚ฐ ์ง€์—ฐ)

ํ•ต์‹ฌ ์ˆ˜์‹

momentum = (์ตœ์‹  ํ™•๋ฅ  - ๊ฐ€์žฅ ์˜ค๋ž˜๋œ ํ™•๋ฅ ) / ์Šค๋ƒ…์ƒท ์ˆ˜    # 5๋ถ„ ์ฃผ๊ธฐ ์ž์ฒด ์Šค๋ƒ…์ƒท, ๋์  ๊ธฐ์šธ๊ธฐ
short = momentum(์ตœ๊ทผ 3๊ฐœ โ‰ˆ 15๋ถ„),  long = momentum(์ตœ๊ทผ 72๊ฐœ โ‰ˆ 6์‹œ๊ฐ„)

BUY : 0.85 โ‰ค p โ‰ค 0.97 (์ƒํ•œ ํฌํ•จ)
      AND ( short - long โ‰ฅ +0.02  AND  long > 0        # golden cross
            OR ์Šค๋ƒ…์ƒท < 6๊ฐœ AND short > 0 )              # cold-start fallback
SELL: p โ‰ฅ 0.97 โ”‚ P&L โ‰ค -10% โ”‚ P&L โ‰ฅ +7% โ”‚ short - long โ‰ค -0.02 (dead cross)

์ „๋žต ๊ฐœ์š”

Golden Banana๋Š” Polymarket์—์„œ ํ™•๋ฅ  85~97% ๊ตฌ๊ฐ„์˜ ๊ณ ํ™•๋ฅ  outcome์„ "๋ชจ๋ฉ˜ํ…€์ด ์ƒ์Šน ์ค‘์ผ ๋•Œ๋งŒ" ๋งค์ˆ˜ํ•˜๋Š” momentum ์ „๋žต ๋ด‡์ด๋‹ค. ๊ตฌ๋ฒ„์ „(golden-apple ๊ณ„์—ด)์˜ "80% ๋งค์ˆ˜ โ†’ 90% ๋งค๋„" ์ •์  ์ž„๊ณ„๊ฐ’ ์ „๋žต์„ ๊ฐœ์„ ํ•œ ๊ฒƒ์œผ๋กœ, 5๋ถ„ ์ฃผ๊ธฐ์˜ Jenkins ์‹คํ–‰๋งˆ๋‹ค ์ „์ฒด ์‹œ์žฅ ํ™•๋ฅ ์„ SQLite market_snapshots ํ…Œ์ด๋ธ”์— ์ž์ฒด ์ถ•์ ํ•˜๊ณ , ์ด ์Šค๋ƒ…์ƒท์œผ๋กœ ๋‹จ๊ธฐ(15๋ถ„=3 ์Šค๋ƒ…์ƒท)ยท์žฅ๊ธฐ(6์‹œ๊ฐ„=72 ์Šค๋ƒ…์ƒท) momentum์„ ๊ณ„์‚ฐํ•ด golden cross(๋‹จ๊ธฐ-์žฅ๊ธฐ โ‰ฅ +2%p/์Šค๋ƒ…์ƒท) ๋ฐœ์ƒ ์‹œ ์ง„์ž…ํ•œ๋‹ค.

์ฒญ์‚ฐ์€ 4์ค‘ ์กฐ๊ฑด(ํ™•๋ฅ  97% ๋„๋‹ฌ, ์ง„์ž…๊ฐ€ ๋Œ€๋น„ +7% ์ด์ต์‹คํ˜„, -10% ์†์ ˆ, dead cross)์œผ๋กœ ๋‹ค๋ณ€ํ™”ํ–ˆ๋‹ค. ์ฃผ๋ฌธ์€ CLOB v2์— midpoint ๊ฐ€๊ฒฉ์˜ GTC limit order๋กœ ์‹คํ–‰๋˜๋ฉฐ, ํ•œ ๋ฒˆ ๊ฑฐ๋ž˜ํ•œ ์‹œ์žฅ์€ ์˜๊ตฌํžˆ ์žฌ๊ฑฐ๋ž˜ํ•˜์ง€ ์•Š๋Š”๋‹ค.

๋งค์ˆ˜ยท๋งค๋„ ์กฐ๊ฑด

๊ฐ€๊ฒฉ ์ด๋ ฅ ์ˆ˜์ง‘ โ€” ํ•ต์‹ฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜: ์™ธ๋ถ€ price-history API๋ฅผ ์“ฐ์ง€ ์•Š๊ณ  ๋ด‡์ด ์Šค์Šค๋กœ ์ด๋ ฅ์„ ๋งŒ๋“ ๋‹ค. ๋งค ์‚ฌ์ดํด Phase 0์—์„œ Gamma ์ „๋Ÿ‰ ์กฐํšŒ(100๊ฐœ์”ฉ ํŽ˜์ด์ง€๋„ค์ด์…˜, offset โ‰ฅ 5,000 ์•ˆ์ „ ํ•œ๋„) ํ›„ min_liquidity ์ด์ƒ + ๋น„์Šคํฌ์ธ  ์‹œ์žฅ๋งˆ๋‹ค ๋†’์€ ์ชฝ ํ™•๋ฅ ์„ market_snapshots์— 1ํ–‰ INSERT(์‹ค์ธก ๋กœ๊ทธ ๊ธฐ์ค€ ์‚ฌ์ดํด๋‹น ์•ฝ 30~35ํ–‰). ํŒ๋‹จ ์‹œ ์ตœ์‹  82๊ฐœ(long_window+10)๋ฅผ ์กฐํšŒํ•˜๋ฉฐ, ์Šค๋ƒ…์ƒท ๊ฐ„๊ฒฉ = Jenkins cron ๊ฐ„๊ฒฉ(5๋ถ„ ๊ฐ€์ •)์ด์ง€๋งŒ timestamp ๊ฐ„๊ฒฉ์€ ๊ฒ€์ฆํ•˜์ง€ ์•Š๊ณ  ๊ฐœ์ˆ˜ ๊ธฐ๋ฐ˜ ์œˆ๋„์šฐ๋งŒ ์‚ฌ์šฉํ•œ๋‹ค. 7์ผ ์ง€๋‚œ ์Šค๋ƒ…์ƒท์€ Phase 4์—์„œ ์‚ญ์ œ.

momentum ๊ณ„์‚ฐ: (์ตœ์‹  ํ™•๋ฅ  - ๊ฐ€์žฅ ์˜ค๋ž˜๋œ ํ™•๋ฅ ) / len(snapshots) โ€” ์ด๋™ํ‰๊ท (MA) cross๊ฐ€ ์•„๋‹ˆ๋ผ ์–‘ ๋์  ๊ธฐ์šธ๊ธฐ(rate of change) ๋น„๊ต๋‹ค. ๋‹จ๊ธฐ๋Š” ์ตœ๊ทผ 3๊ฐœ(3๊ฐœ ๋ฏธ๋งŒ์ด๋ฉด ์ง„์ž… ๋ถˆ๊ฐ€), ์žฅ๊ธฐ๋Š” ์ตœ๊ทผ 72๊ฐœ(72๊ฐœ ๋ฏธ๋งŒ์ด์–ด๋„ 6๊ฐœ ์ด์ƒ์ด๋ฉด ์žˆ๋Š” ๋งŒํผ ์‚ฌ์šฉ, 6๊ฐœ ๋ฏธ๋งŒ์ด๋ฉด None). ์žฅ๊ธฐ๊ฐ€ None์ด๋ฉด cold-start fallback์œผ๋กœ ๋‹จ๊ธฐ momentum > 0 ๋งŒ์œผ๋กœ ์ง„์ž…์„ ํ—ˆ์šฉํ•˜๋ฉฐ(short_momentum_positive), ์‹ค์šด์˜ ๋กœ๊ทธ(20260205)์—์„œ ์‹ค์ œ ๋งค์ˆ˜๋Š” ์ „๋ถ€ ์ด ๊ฒฝ๋กœ์˜€๋‹ค.

๋งค์ˆ˜ ํŒŒ์ดํ”„๋ผ์ธ โ€” ์กฐ๊ฑด ์ „๋ถ€ AND, ์ˆœ์„œ๋Œ€๋กœ:

#์กฐ๊ฑด๋น„๊ณ 
1liquidity โ‰ฅ 50,000Gamma ์ „๋Ÿ‰ ์กฐํšŒ์—์„œ ํ•„ํ„ฐ
2์Šคํฌ์ธ  ์•„๋‹˜tags + question/slug ํ…์ŠคํŠธ์— SPORTS_KEYWORDS(์•ฝ 50๊ฐœ; "game", "match", "win the", "beat", "score" ๋“ฑ) ์—†์–ด์•ผ ํ•จ
3favorite ์„ ํƒoutcomePrices ๋‘ ๊ฐ’ ์ค‘ ํฐ ์ชฝ + token_id ์กด์žฌ. Yes/No ์ค‘ ๋†’์€ ์ชฝ์„ ์‚ฐ๋‹ค
40.85 โ‰ค p โ‰ค 0.97์ƒํ•œ ํฌํ•จ
5momentum ์ง„์ž… ์‹œ๊ทธ๋„golden_cross ๋˜๋Š” cold-start short_momentum_positive (momentum ๋น„ํ™œ์„ฑ ์‹œ ๋ฌด์กฐ๊ฑด ํ†ต๊ณผ)
6๋ฏธ๊ฑฐ๋ž˜ ์‹œ์žฅtrades/skipped_markets์— ์žˆ์œผ๋ฉด ์˜๊ตฌ skip
7max_positions ์ œํ•œ๊ธฐ๋ณธ -1(๋ฌด์ œํ•œ)
8CLOB midpoint ์žฌ๊ฒ€์ฆ> 0.97 โ†’ rapid_jump ์˜๊ตฌ skip / < 0.85 โ†’ ์ด๋ฒˆ ์‚ฌ์ดํด๋งŒ skip
9์ˆ˜๋Ÿ‰ = buy_amount รท midpoint โ‰ฅ 5์ฃผ์ตœ์†Œ ์ฃผ๋ฌธ ์ˆ˜๋Ÿ‰
10GTC limit BUY @ midpoint0.01 tick ๋ฐ˜์˜ฌ๋ฆผ, [0.01, 0.99] clamp. orderID ์‘๋‹ต ์‹œ ์ฆ‰์‹œ HOLDING ๊ธฐ๋ก(์ฒด๊ฒฐ ํ™•์ธ ์—†์Œ). entry_reasonยทmomentum ๊ฐ’ ์ €์žฅ

๋งค๋„ ํŒŒ์ดํ”„๋ผ์ธ โ€” ๋ณด์œ  ํฌ์ง€์…˜๋งˆ๋‹ค ๋งค ์‚ฌ์ดํด, ์šฐ์„ ์ˆœ์œ„ ์ˆœ:

์šฐ์„ ์ˆœ์œ„์กฐ๊ฑดexit_reason
1CLOB midpoint โ‰ฅ 0.97 (sell_threshold)threshold
2(ํ˜„์žฌ๊ฐ€-์ง„์ž…๊ฐ€)/์ง„์ž…๊ฐ€ โ‰ค -0.10stop_loss
3โ‰ฅ +0.07take_profit
4short - long โ‰ค -0.02 (์Šค๋ƒ…์ƒท ์ถฉ๋ถ„ํ•  ๋•Œ๋งŒ)dead_cross

momentum ๋น„ํ™œ์„ฑ ์‹œ์—๋Š” 2ยท3๋งŒ ๊ฒ€์‚ฌ. ๋งค๋„ ์ฃผ๋ฌธ๋„ ํ˜„์žฌ midpoint ๊ฐ€๊ฒฉ GTC limit SELL์ด๋ฉฐ, ์ ‘์ˆ˜ ์ฆ‰์‹œ COMPLETED + realized_pnl = (๋งค๋„๊ฐ€-๋งค์ˆ˜๊ฐ€)ร—์ˆ˜๋Ÿ‰ ๊ธฐ๋ก. exit_reasonยท๋งค๋„ ์‹œ์  momentum ๊ฐ’ ์ €์žฅ.

ํŒŒ๋ผ๋ฏธํ„ฐ ์ดํ‘œ (์šฐ์„ ์ˆœ์œ„: env var > config.yaml > ์ฝ”๋“œ ๊ธฐ๋ณธ๊ฐ’)

ํŒŒ๋ผ๋ฏธํ„ฐyaml ๊ฐ’์ฝ”๋“œ ๊ธฐ๋ณธ๊ฐ’env var์˜๋ฏธ
buy_threshold0.850.85POLYBOT_BUY_THRESHOLD๋งค์ˆ˜ ์ตœ์†Œ ํ™•๋ฅ 
sell_threshold0.970.97POLYBOT_SELL_THRESHOLD๋งค์ˆ˜ ์ƒํ•œ์ด์ž ํ™•๋ฅ  ๊ธฐ์ค€ ๋งค๋„ ํŠธ๋ฆฌ๊ฑฐ
buy_amount_usdc5.010.0POLYBOT_BUY_AMOUNT1ํšŒ ๋งค์ˆ˜ ๊ธˆ์•ก (USDC)
min_liquidity5000050000.0POLYBOT_MIN_LIQUIDITY์ตœ์†Œ ์œ ๋™์„ฑ ํ•„ํ„ฐ ($)
max_positions-1 (๋ฌด์ œํ•œ)-1POLYBOT_MAX_POSITIONS๋™์‹œ ๋ณด์œ  ํฌ์ง€์…˜ ์ƒํ•œ
take_profit_percent0.070.07POLYBOT_TAKE_PROFIT์ง„์ž…๊ฐ€ ๋Œ€๋น„ ์ด์ต์‹คํ˜„ (+7%)
stop_loss_percent-0.10-0.10POLYBOT_STOP_LOSS์ง„์ž…๊ฐ€ ๋Œ€๋น„ ์†์ ˆ (-10%)
momentum.enabledtruetruePOLYBOT_MOMENTUM_ENABLEDmomentum ํ•„ํ„ฐ on/off
momentum.short_window33POLYBOT_MOMENTUM_SHORT_WINDOW๋‹จ๊ธฐ ์œˆ๋„์šฐ (3 ์Šค๋ƒ…์ƒท โ‰ˆ 15๋ถ„)
momentum.long_window7272POLYBOT_MOMENTUM_LONG_WINDOW์žฅ๊ธฐ ์œˆ๋„์šฐ (72 ์Šค๋ƒ…์ƒท โ‰ˆ 6์‹œ๊ฐ„)
momentum.golden_cross_threshold0.020.02POLYBOT_GOLDEN_CROSS_THRESHOLD์ง„์ž…: ๋‹จ๊ธฐ-์žฅ๊ธฐ โ‰ฅ ์ด ๊ฐ’
momentum.dead_cross_threshold-0.02-0.02POLYBOT_DEAD_CROSS_THRESHOLD์ฒญ์‚ฐ: ๋‹จ๊ธฐ-์žฅ๊ธฐ โ‰ค ์ด ๊ฐ’
momentum.require_positive_long_momentumtruetruePOLYBOT_REQUIRE_POSITIVE_LONG_MOMENTUMgolden cross์—ฌ๋„ ์žฅ๊ธฐ momentum โ‰ค 0์ด๋ฉด ์ง„์ž… ๊ฑฐ๋ถ€
excluded_categoriesSports ๊ณ„์—ด 10์ข…๋™์ผ(env ์—†์Œ)์ œ์™ธ ์นดํ…Œ๊ณ ๋ฆฌ
simulation_modefalsefalse(CLI --simulate๋กœ๋งŒ override)์‹ค์ฃผ๋ฌธ ์—ฌ๋ถ€
(์ƒ์ˆ˜) MIN_ORDER_SIZE-5.0-์ตœ์†Œ ์ฃผ๋ฌธ ์ˆ˜๋Ÿ‰ 5์ฃผ
(์ƒ์ˆ˜) DEFAULT_TICK_SIZE-0.01-๊ฐ€๊ฒฉ tick ๋ฐ˜์˜ฌ๋ฆผ, [0.01, 0.99] clamp

ํ•„์ˆ˜ credential์€ POLYMARKET_PRIVATE_KEYยทPOLYMARKET_FUNDER_ADDRESS(์—†์œผ๋ฉด ์ฆ‰์‹œ ์ข…๋ฃŒ, private key์˜ hex prefix๋Š” ์ž๋™ ์ œ๊ฑฐ). SLACK_WEBHOOK_URL์€ ํ˜„์žฌ ์‚ฌ์ดํด์— ๋ฏธ์—ฐ๊ฒฐ.

์ด์šฉํ•˜๋Š” ์‹ฌ๋ฆฌ

๊ฐ•์ 

์•ฝ์  (ํ—ˆ์ )

๊ฐœ์„  ๋ฐ˜์˜์ฒ˜

A/B ๊ฒ€์ฆ ๊ณ„ํš

โ‘  ์‹œ๋ฎฌ๋ ˆ์ด์…˜: polybot run --simulate๋Š” ์‹ค๊ฑฐ๋ž˜์™€ ๋™์ผ ๊ฒฝ๋กœ์—์„œ ์ฃผ๋ฌธ๋งŒ mock(trades_sim.db ๋ถ„๋ฆฌ). ๋‹จ, midpoint 100% ์ฒด๊ฒฐ ๊ฐ€์ •์ด๋ผ sim P&L์€ ๋‚™๊ด€ ํŽธํ–ฅ(์•ฝ์  14)์ด ์žˆ๊ณ , scripts/simulate.py๋Š” DB ๊ฒฝ๋กœ ๋ฒ„๊ทธ(์•ฝ์  12)๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ --simulate ํ”Œ๋ž˜๊ทธ ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. โ‘ก ์†Œ์•ก ์‹ค๊ฑฐ๋ž˜: yaml buy_amount_usdc: 5.0์œผ๋กœ ๊ฑด๋‹น ๋…ธ์ถœ ์ œํ•œ. โ‘ข ํŒ๋‹จ ํ›„ ์ฆ์•ก: ๋งค ์‚ฌ์ดํด ๋กœ๊ทธ์˜ momentum ๋ถ„์„ ์š”์•ฝ ํ‘œ(์ง„์ž…/์ œ์™ธ ์‚ฌ์œ , ๋‹จ๊ธฐ/์žฅ๊ธฐ diff, ์Šค๋ƒ…์ƒท ์ˆ˜)์™€ entry_reason/exit_reason ์ปฌ๋Ÿผ ์ง‘๊ณ„๋กœ "golden cross ๊ฒฝ๋กœ ๋Œ€๋น„ cold-start ๊ฒฝ๋กœ"์˜ ์‹ค์ ์„ ๋น„๊ตํ•ด ์ž„๊ณ„๊ฐ’ ์žฌ๋ณด์ • ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค.

์‹คํ–‰ (Jenkins)

# Jenkins cron (5๋ถ„ ์ฃผ๊ธฐ, one-shot โ€” ๋ด‡ ๋‚ด๋ถ€ ๋ฃจํ”„ ์—†์Œ)
cd ./golden-banana
export POLYMARKET_PRIVATE_KEY=<Jenkins credential>
export POLYMARKET_FUNDER_ADDRESS=<Jenkins credential>
uv sync --frozen
uv run polybot run                   # ์ƒํƒœ๋Š” data/{job}/trades.db ์— ๊ฒฉ๋ฆฌ