feat: initial upbit auto-trader implementation

Strategy C: volatility breakout (Larry Williams K=0.5) AND momentum
(MA20 + 2x volume surge) must both trigger for a buy signal.

Hard rules:
- Trailing stop: sell when price drops -10% from peak
- Max budget: 1,000,000 KRW total, up to 3 positions (333,333 KRW each)
- Scan top 20 KRW tickers by 24h trading volume every 60s
- Monitor positions every 10s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
joungmin
2026-02-28 10:20:02 +09:00
commit 83bd51117f
11 changed files with 404 additions and 0 deletions

58
core/monitor.py Normal file
View File

@@ -0,0 +1,58 @@
"""트레일링 스탑 감시 - 백그라운드 스레드에서 실행."""
import logging
import time
from .market import get_current_price
from . import trader
logger = logging.getLogger(__name__)
STOP_LOSS_PCT = 0.10 # 최고가 대비 -10%
CHECK_INTERVAL = 10 # 10초마다 체크
def _check_stop(ticker: str, pos: dict) -> None:
"""단일 포지션 스탑 체크."""
current = get_current_price(ticker)
if current is None:
return
# 최고가 갱신
trader.update_peak(ticker, current)
# 최신 peak_price 읽기 (update 후)
pos = trader.get_positions().get(ticker)
if pos is None:
return
peak = pos["peak_price"]
buy_price = pos["buy_price"]
drop_from_peak = (peak - current) / peak
# 로그 (보유 중 상태 확인)
pnl = (current - buy_price) / buy_price * 100
logger.info(
f"[감시] {ticker} 현재={current:,.0f} | "
f"최고={peak:,.0f} | 하락={drop_from_peak:.1%} | 수익률={pnl:+.1f}%"
)
if drop_from_peak >= STOP_LOSS_PCT:
reason = (
f"트레일링스탑 | 최고가={peak:,.0f}원 → "
f"현재={current:,.0f}원 ({drop_from_peak:.1%} 하락)"
)
trader.sell(ticker, reason=reason)
def run_monitor(interval: int = CHECK_INTERVAL) -> None:
"""전체 포지션 감시 루프."""
logger.info(f"모니터 시작 (체크 주기={interval}초, 스탑={STOP_LOSS_PCT:.0%})")
while True:
positions_snapshot = dict(trader.get_positions())
for ticker, pos in positions_snapshot.items():
try:
_check_stop(ticker, pos)
except Exception as e:
logger.error(f"모니터 오류 {ticker}: {e}")
time.sleep(interval)