feat: OpenRouter LLM 매도 어드바이저 + 종목 컨텍스트 수집 데몬
- llm_advisor: Anthropic → OpenRouter API 전환 (claude-haiku-4.5) - llm_advisor: get_ticker_context DB tool 추가 (24h/7d 가격, 뉴스) - llm_advisor: 구조화 JSON 응답 (confidence, reason, market_status, watch_needed) - llm_advisor: LLM primary + cascade fallback (llm_active 플래그) - llm_advisor: SQL bind variable 버그 수정 (INTERVAL → NUMTODSINTERVAL) - tick_collector: backtest_ohlcv 1분봉 실시간 갱신 추가 (60초 주기) - context_collector: 신규 데몬 — 1시간마다 price_stats + SearXNG 뉴스 수집 - ecosystem: tick-collector, tick-trader, context-collector PM2 등록 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
118
daemons/tick_collector.py
Normal file
118
daemons/tick_collector.py
Normal file
@@ -0,0 +1,118 @@
|
||||
"""30초마다 전 종목 현재가를 Oracle price_tick 테이블에 적재.
|
||||
+ 60초마다 backtest_ohlcv 1분봉 최신 데이터 갱신.
|
||||
"""
|
||||
import sys, os, time, logging
|
||||
from datetime import datetime
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), '.env'))
|
||||
|
||||
import pyupbit
|
||||
import oracledb
|
||||
|
||||
TICKERS = [
|
||||
'KRW-XRP', 'KRW-BTC', 'KRW-ETH', 'KRW-SOL', 'KRW-DOGE',
|
||||
'KRW-ADA', 'KRW-SUI', 'KRW-NEAR', 'KRW-KAVA', 'KRW-SXP',
|
||||
'KRW-AKT', 'KRW-SONIC', 'KRW-IP', 'KRW-ORBS', 'KRW-VIRTUAL',
|
||||
'KRW-BARD', 'KRW-XPL', 'KRW-KITE', 'KRW-ENSO', 'KRW-0G',
|
||||
'KRW-MANTRA', 'KRW-EDGE', 'KRW-CFG', 'KRW-ARDR', 'KRW-SIGN',
|
||||
'KRW-AZTEC', 'KRW-ATH', 'KRW-HOLO', 'KRW-BREV', 'KRW-SHIB',
|
||||
]
|
||||
INTERVAL = 30 # 초
|
||||
OHLCV_INTERVAL = 60 # 1분봉 갱신 주기 (초)
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s %(levelname)s %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler('/tmp/tick_collector.log'),
|
||||
logging.StreamHandler(sys.stdout),
|
||||
]
|
||||
)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_conn():
|
||||
kwargs = dict(user=os.environ["ORACLE_USER"], password=os.environ["ORACLE_PASSWORD"],
|
||||
dsn=os.environ["ORACLE_DSN"])
|
||||
if w := os.environ.get("ORACLE_WALLET"):
|
||||
kwargs["config_dir"] = w
|
||||
return oracledb.connect(**kwargs)
|
||||
|
||||
|
||||
def collect(conn):
|
||||
prices = pyupbit.get_current_price(TICKERS)
|
||||
if not prices:
|
||||
log.warning("현재가 조회 실패")
|
||||
return
|
||||
|
||||
ts = datetime.now().replace(microsecond=0)
|
||||
rows = [(t, ts, p) for t, p in prices.items() if p]
|
||||
|
||||
cur = conn.cursor()
|
||||
cur.executemany(
|
||||
"INSERT INTO price_tick (ticker, ts, price) VALUES (:1, :2, :3)",
|
||||
rows
|
||||
)
|
||||
conn.commit()
|
||||
log.info(f"적재 {len(rows)}건 ts={ts}")
|
||||
|
||||
|
||||
def refresh_ohlcv(conn):
|
||||
"""backtest_ohlcv 1분봉을 최근 5개씩 갱신 (중복 무시)."""
|
||||
total = 0
|
||||
for ticker in TICKERS:
|
||||
try:
|
||||
df = pyupbit.get_ohlcv(ticker, interval='minute1', count=5)
|
||||
if df is None or df.empty:
|
||||
continue
|
||||
rows = [
|
||||
(ticker, 'minute1', ts.to_pydatetime(),
|
||||
float(r['open']), float(r['high']), float(r['low']),
|
||||
float(r['close']), float(r['volume']))
|
||||
for ts, r in df.iterrows()
|
||||
]
|
||||
cur = conn.cursor()
|
||||
cur.executemany(
|
||||
"INSERT INTO backtest_ohlcv "
|
||||
"(ticker,interval_cd,ts,open_p,high_p,low_p,close_p,volume_p) "
|
||||
"VALUES (:1,:2,:3,:4,:5,:6,:7,:8)",
|
||||
rows, batcherrors=True,
|
||||
)
|
||||
inserted = len(rows) - len(cur.getbatcherrors())
|
||||
total += inserted
|
||||
time.sleep(0.15)
|
||||
except Exception as e:
|
||||
log.warning(f"[ohlcv] {ticker} 오류: {e}")
|
||||
conn.commit()
|
||||
if total > 0:
|
||||
log.info(f"[ohlcv] 1분봉 갱신 {total}건")
|
||||
|
||||
|
||||
def main():
|
||||
log.info("=== tick_collector 시작 (30초 간격 + 1분봉 갱신) ===")
|
||||
conn = get_conn()
|
||||
last_ohlcv = 0
|
||||
while True:
|
||||
t0 = time.time()
|
||||
try:
|
||||
collect(conn)
|
||||
# 1분봉 갱신 (OHLCV_INTERVAL마다)
|
||||
if t0 - last_ohlcv >= OHLCV_INTERVAL:
|
||||
refresh_ohlcv(conn)
|
||||
last_ohlcv = t0
|
||||
except oracledb.DatabaseError as e:
|
||||
log.error(f"DB 오류: {e} — 재연결")
|
||||
try: conn.close()
|
||||
except: pass
|
||||
conn = get_conn()
|
||||
except Exception as e:
|
||||
log.error(f"오류: {e}")
|
||||
|
||||
elapsed = time.time() - t0
|
||||
time.sleep(max(1.0, INTERVAL - elapsed))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user