Files
upbit-trader/core/strategy.py
joungmin 0b264b304c feat: add backtest module with DB cache and scenario comparison
Backtest improvements:
- Add backtest.py with Oracle DB-backed OHLCV cache (no repeated API calls)
- Add backtest_trades table to cache simulation results by params hash
  (same params -> instant load, skip re-simulation)
- Add walk-forward scenario comparison (--walkforward-cmp)
- Add trend ceiling filter (--trend-cmp, max gain threshold)
- Add ticker win-rate filter (--ticker-cmp, SQL-based instant analysis)
- Precompute daily_features once per data load (not per scenario)

Live bot fixes:
- monitor: add hard stop-loss from buy price (in addition to trailing)
- strategy: fix re-entry condition to require +1% above last sell price
- price_collector: add 48h backfill on startup for trend calculation
- main: call backfill_prices() at startup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 23:28:27 +09:00

79 lines
2.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Strategy C: 현재 기준 N시간 전 대비 상승 추세(DB) AND 거래량 모멘텀 동시 충족 시 매수 신호."""
from __future__ import annotations
import logging
import os
from .market import get_current_price, get_ohlcv
from .price_db import get_price_n_hours_ago
logger = logging.getLogger(__name__)
# 추세 판단: 현재 기준 N시간 전 DB 가격 대비 +M% 이상이면 상승 중
TREND_HOURS = float(os.getenv("TREND_HOURS", "12"))
TREND_MIN_GAIN_PCT = float(os.getenv("TREND_MIN_GAIN_PCT", "3"))
# 모멘텀: MA 기간, 거래량 급증 배수
MA_PERIOD = 20
VOLUME_MULTIPLIER = 2.0
def check_trend(ticker: str) -> bool:
"""상승 추세 조건: 현재가가 DB에 저장된 N시간 전 가격 대비 +M% 이상."""
past_price = get_price_n_hours_ago(ticker, TREND_HOURS)
if past_price is None:
logger.debug(f"[추세] {ticker} {TREND_HOURS:.0f}h 전 가격 없음 (수집 중)")
return False
current = get_current_price(ticker)
if not current:
return False
gain_pct = (current - past_price) / past_price * 100
result = gain_pct >= TREND_MIN_GAIN_PCT
if result:
logger.info(
f"[추세↑] {ticker} {TREND_HOURS:.0f}h 전={past_price:,.2f} "
f"현재={current:,.2f} (+{gain_pct:.1f}%)"
)
else:
logger.debug(
f"[추세✗] {ticker} {gain_pct:+.1f}% (기준={TREND_MIN_GAIN_PCT:+.0f}%)"
)
return result
def check_momentum(ticker: str) -> bool:
"""모멘텀 조건: 현재가 > MA20 AND 오늘 거래량 > 20일 평균 × 2."""
df = get_ohlcv(ticker, count=MA_PERIOD + 1)
if df is None or len(df) < MA_PERIOD + 1:
return False
ma = df["close"].iloc[-MA_PERIOD:].mean()
avg_vol = df["volume"].iloc[:-1].mean()
today_vol = df["volume"].iloc[-1]
current = get_current_price(ticker)
if current is None:
return False
price_ok = current > ma
vol_ok = today_vol > avg_vol * VOLUME_MULTIPLIER
result = price_ok and vol_ok
if result:
logger.debug(
f"[모멘텀] {ticker} 현재={current:,.0f} MA20={ma:,.0f} "
f"거래량={today_vol:.0f} 평균={avg_vol:.0f}"
)
return result
def should_buy(ticker: str) -> bool:
"""Strategy C: 실시간 상승 추세 AND 거래량 모멘텀 모두 충족 시 True."""
if not check_trend(ticker):
return False
return check_momentum(ticker)