feat: add Fear & Greed filter to entry logic

- core/fng.py: F&G API wrapper with 1h cache (alternative.me)
  - FNG_MIN_ENTRY=41 (env-configurable), blocks entry below threshold
- core/strategy.py: call is_entry_allowed() before volume/regime checks
- daemon/runner.py: log F&G status on every scan cycle
- core/notify.py: include F&G value in buy/signal/status notifications
- core/trader.py: pass current F&G value to notify_buy

Backtest evidence (1y / 18 tickers / 1h candles):
  - No filter:   820 trades, 32.7% WR, avg +0.012%, KRW +95k
  - F&G >= 41:   372 trades, 39.5% WR, avg +0.462%, KRW +1.72M
  - Blocked 452 trades (avg -0.372%, saved ~1.68M KRW loss)

Also add:
- backtest_db.py: Oracle DB storage for backtest runs/results/trades
- fng_1y_backtest.py, fng_adaptive_backtest.py, fng_sim_comparison.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
joungmin
2026-03-03 15:56:17 +09:00
parent 673ce08d84
commit 27189b1ad9
9 changed files with 1402 additions and 8 deletions

View File

@@ -6,6 +6,7 @@ import threading
import time
from core import trader
from core.fng import FNG_MIN_ENTRY, get_fng
from core.market import get_top_tickers
from core.market_regime import get_regime
from core.strategy import get_active_signals, should_buy
@@ -73,8 +74,18 @@ def run_scanner() -> None:
time.sleep(SCAN_INTERVAL)
continue
# F&G 진입 필터 로그 (should_buy 내부에서 차단하지만 스캔 전 상태 기록)
fv = get_fng()
fng_label = (
"극탐욕" if fv >= 76 else "탐욕" if fv >= 56 else
"중립" if fv >= 46 else "약공포" if fv >= 41 else
"공포" if fv >= 26 else "극공포"
)
if fv < FNG_MIN_ENTRY:
logger.info(f"[F&G차단] F&G={fv} ({fng_label}) < {FNG_MIN_ENTRY} — 이번 스캔 진입 없음")
tickers = get_top_tickers()
logger.info(f"스캔 시작: {len(tickers)}개 종목")
logger.info(f"스캔 시작: {len(tickers)}개 종목 | F&G={fv}({fng_label})")
for ticker in tickers:
# 이미 보유 중인 종목 제외