Files
upbit-trader/core/strategy.py
joungmin 5df56a933e feat: use 60min volume, add KRW P&L log, relax re-entry after win
- strategy: replace daily volume check with 60-min candle volume
  (daily volume at 5am is tiny -> BTC/ETH never matched; now uses
  last 1h candle vs previous 23h avg × 2)
- trader: log actual KRW net profit and fee on every sell
- trader: skip re-entry +1% block when last trade was a win
  (allow re-entry on new trend signal even below last sell price)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 05:38:37 +09:00

94 lines
3.0 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
import pyupbit
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 최근 1h 거래량 > 24h 평균 × 2 (60분봉 기준).
일봉 거래량은 오전에 항상 미달하므로 60분봉으로 교체.
"""
# MA20: 일봉 기준
df_daily = get_ohlcv(ticker, count=MA_PERIOD + 1)
if df_daily is None or len(df_daily) < MA_PERIOD + 1:
return False
ma = df_daily["close"].iloc[-MA_PERIOD:].mean()
current = get_current_price(ticker)
if current is None:
return False
price_ok = current > ma
if not price_ok:
return False
# 거래량: 60분봉 기준 (최근 1h vs 이전 24h 평균)
try:
df_hour = pyupbit.get_ohlcv(ticker, interval="minute60", count=26)
except Exception:
return False
if df_hour is None or len(df_hour) < 5:
return False
recent_vol = df_hour["volume"].iloc[-2] # 직전 완성된 1h 봉
avg_vol_1h = df_hour["volume"].iloc[-25:-2].mean() # 이전 23h 평균
vol_ok = avg_vol_1h > 0 and recent_vol > avg_vol_1h * VOLUME_MULTIPLIER
result = price_ok and vol_ok
if result:
logger.debug(
f"[모멘텀] {ticker} 현재={current:,.0f} MA20={ma:,.0f} "
f"1h거래량={recent_vol:.0f} 평균={avg_vol_1h:.0f}"
)
return result
def should_buy(ticker: str) -> bool:
"""Strategy C: 실시간 상승 추세 AND 거래량 모멘텀 모두 충족 시 True."""
if not check_trend(ticker):
return False
return check_momentum(ticker)