80 lines
2.6 KiB
Python
80 lines
2.6 KiB
Python
"""
|
|
monitor/notifier.py
|
|
디스코드 Webhook 알림 (단방향)
|
|
aiohttp만 사용 - 별도 라이브러리 없음
|
|
"""
|
|
import os
|
|
import asyncio
|
|
import aiohttp
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
WEBHOOK_URL = os.getenv("DISCORD_WEBHOOK_URL", "")
|
|
|
|
async def send(message: str) -> None:
|
|
"""디스코드 Webhook 메시지 전송"""
|
|
if not WEBHOOK_URL:
|
|
logger.warning(f"[Discord 미설정] {message}")
|
|
return
|
|
try:
|
|
async with aiohttp.ClientSession() as session:
|
|
await session.post(
|
|
WEBHOOK_URL,
|
|
json={"content": message},
|
|
timeout=aiohttp.ClientTimeout(total=5)
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Discord 알림 실패: {e}")
|
|
|
|
# ── 이벤트별 메시지 ──
|
|
|
|
async def notify_ai_result(sentiment: str, score: int,
|
|
hot: list, avoid: list, reason: str):
|
|
hot_str = ",".join(hot) if hot else "없음"
|
|
avoid_str = ",".join(avoid) if avoid else "없음"
|
|
await send(
|
|
f"[AI분석] 시장: {sentiment}({score}점) | "
|
|
f"주목: {hot_str} | 회피: {avoid_str}\n💬 {reason}"
|
|
)
|
|
|
|
async def notify_ai_blocked(ticker: str, name: str, reason: str):
|
|
await send(f"[AI차단] {name}({ticker}) - {reason}")
|
|
|
|
async def notify_buy(ticker: str, name: str, price: int,
|
|
target: int, stop: int, boosted: bool = False):
|
|
star = "★ " if boosted else ""
|
|
await send(
|
|
f"[매수{star}] {name}({ticker}) {price:,}원 | "
|
|
f"목표 {target:,} | 손절 {stop:,}"
|
|
)
|
|
|
|
async def notify_tp1(ticker: str, name: str, pct: float):
|
|
await send(f"[익절1] {name}({ticker}) +{pct:.1f}% / 잔여 50%")
|
|
|
|
async def notify_tp2(ticker: str, name: str, pct: float):
|
|
await send(f"[익절2] {name}({ticker}) +{pct:.1f}% / 전량 청산")
|
|
|
|
async def notify_sl(ticker: str, name: str, pct: float):
|
|
await send(f"[손절] {name}({ticker}) {pct:.1f}% / 즉시 청산")
|
|
|
|
async def notify_force_exit():
|
|
await send("[14:50 강제청산] 전 포지션 청산 완료")
|
|
|
|
async def notify_risk(level: str, message: str):
|
|
await send(f"[경고-{level}] {message}")
|
|
|
|
async def notify_daily_summary(trades: int, wins: int,
|
|
losses: int, net_pnl: float):
|
|
win_rate = wins / trades * 100 if trades else 0
|
|
await send(
|
|
f"[결산] 매매 {trades}회 / 승 {wins} 패 {losses} "
|
|
f"({win_rate:.0f}%) / 순손익 {net_pnl:+,.0f}원"
|
|
)
|
|
|
|
async def notify_error(message: str):
|
|
await send(f"[긴급] {message}")
|
|
|
|
async def notify_ai_fallback():
|
|
await send("[경고] AI 판단 실패 → 기본값 적용 (비중 80%)")
|