feat: volume-lead strategy with compounding, WF filter, and DB-backed simulation
- core/strategy.py: replace trend strategy with volume-lead accumulation (vol spike + 2h quiet → signal, +4.8% rise → entry) - core/trader.py: compound budget adjusts on both profit and loss (floor 30%) - core/notify.py: add accumulation signal telegram notification - ohlcv_db.py: Oracle ADB OHLCV cache (insert, load, incremental update) - sim_365.py: 365-day compounding simulation loading from DB - krw_sim.py: KRW-based simulation with MAX_POSITIONS constraint - ticker_sim.py: ticker count expansion comparison - STRATEGY.md: full strategy documentation - .gitignore: exclude *.pkl cache files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,16 @@ def notify_sell(
|
||||
)
|
||||
|
||||
|
||||
def notify_signal(ticker: str, signal_price: float, vol_mult: float) -> None:
|
||||
"""거래량 축적 신호 감지 알림."""
|
||||
_send(
|
||||
f"🔍 <b>[축적감지]</b> {ticker}\n"
|
||||
f"신호가: {signal_price:,.2f}원\n"
|
||||
f"거래량: {vol_mult:.1f}x 급증 + 2h 횡보\n"
|
||||
f"진입 목표: {signal_price * 1.048:,.2f}원 (+4.8%)"
|
||||
)
|
||||
|
||||
|
||||
def notify_error(message: str) -> None:
|
||||
_send(f"⚠️ <b>[오류]</b>\n{message}")
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import pyupbit
|
||||
|
||||
from .market import get_current_price
|
||||
from .market_regime import get_regime
|
||||
from .notify import notify_signal
|
||||
from .price_db import get_price_n_hours_ago
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -112,6 +113,17 @@ def should_buy(ticker: str) -> bool:
|
||||
logger.info(
|
||||
f"[축적감지] {ticker} 거래량 급증 + 2h 횡보 → 신호가={current:,.2f}원"
|
||||
)
|
||||
# 거래량 비율 계산 후 알림 전송
|
||||
try:
|
||||
fetch_count = LOCAL_VOL_HOURS + 3
|
||||
df_h = pyupbit.get_ohlcv(ticker, interval="minute60", count=fetch_count)
|
||||
if df_h is not None and len(df_h) >= LOCAL_VOL_HOURS + 1:
|
||||
recent_vol = df_h["volume"].iloc[-2]
|
||||
local_avg = df_h["volume"].iloc[-(LOCAL_VOL_HOURS + 1):-2].mean()
|
||||
ratio = recent_vol / local_avg if local_avg > 0 else 0
|
||||
notify_signal(ticker, current, ratio)
|
||||
except Exception:
|
||||
notify_signal(ticker, current, 0.0)
|
||||
return False # 신호 첫 발생 시는 진입 안 함
|
||||
|
||||
# ── 신호 있음: 상승 확인 → 진입 ─────────────────────────
|
||||
|
||||
@@ -33,21 +33,22 @@ if SIMULATION_MODE:
|
||||
INITIAL_BUDGET = int(os.getenv("MAX_BUDGET", "10000000")) # 초기 원금 (고정)
|
||||
MAX_POSITIONS = int(os.getenv("MAX_POSITIONS", "3")) # 최대 동시 보유 종목 수
|
||||
|
||||
# 복리 적용 예산 (매도 후 재계산) — 수익 발생 시만 증가, 손실 시 원금 유지
|
||||
# 복리 적용 예산 (매도 후 재계산) — 수익 시 복리 증가, 손실 시 차감 (하한 30%)
|
||||
MIN_BUDGET = INITIAL_BUDGET * 3 // 10 # 최소 예산: 초기값의 30%
|
||||
MAX_BUDGET = INITIAL_BUDGET
|
||||
PER_POSITION = INITIAL_BUDGET // MAX_POSITIONS
|
||||
|
||||
|
||||
def _recalc_compound_budget() -> None:
|
||||
"""누적 수익을 반영해 MAX_BUDGET / PER_POSITION 재계산.
|
||||
"""누적 수익/손실을 반영해 MAX_BUDGET / PER_POSITION 재계산.
|
||||
|
||||
수익이 발생한 만큼만 예산에 더함 (손실 시 원금 아래로 내려가지 않음).
|
||||
수익 시 복리로 증가, 손실 시 차감 (최소 초기 예산의 30% 보장).
|
||||
매도 완료 후 호출.
|
||||
"""
|
||||
global MAX_BUDGET, PER_POSITION
|
||||
try:
|
||||
cum_profit = get_cumulative_krw_profit()
|
||||
effective = INITIAL_BUDGET + max(int(cum_profit), 0)
|
||||
effective = max(INITIAL_BUDGET + int(cum_profit), MIN_BUDGET)
|
||||
MAX_BUDGET = effective
|
||||
PER_POSITION = effective // MAX_POSITIONS
|
||||
logger.info(
|
||||
|
||||
Reference in New Issue
Block a user