71 lines
2.2 KiB
Python
71 lines
2.2 KiB
Python
import httpx
|
||
from loguru import logger
|
||
|
||
|
||
class DiscordNotifier:
|
||
"""Discord 웹훅으로 거래 알림을 전송하는 노티파이어."""
|
||
|
||
def __init__(self, webhook_url: str):
|
||
self.webhook_url = webhook_url
|
||
self._enabled = bool(webhook_url)
|
||
|
||
def _send(self, content: str) -> None:
|
||
if not self._enabled:
|
||
logger.debug("Discord 웹훅 URL 미설정 - 알림 건너뜀")
|
||
return
|
||
try:
|
||
resp = httpx.post(
|
||
self.webhook_url,
|
||
json={"content": content},
|
||
timeout=10,
|
||
)
|
||
resp.raise_for_status()
|
||
except Exception as e:
|
||
logger.warning(f"Discord 알림 전송 실패: {e}")
|
||
|
||
def notify_open(
|
||
self,
|
||
symbol: str,
|
||
side: str,
|
||
entry_price: float,
|
||
quantity: float,
|
||
leverage: int,
|
||
stop_loss: float,
|
||
take_profit: float,
|
||
signal_data: dict = None,
|
||
) -> None:
|
||
rsi = (signal_data or {}).get("rsi", 0)
|
||
macd = (signal_data or {}).get("macd_hist", 0)
|
||
atr = (signal_data or {}).get("atr", 0)
|
||
msg = (
|
||
f"**[{symbol}] {side} 진입**\n"
|
||
f"진입가: `{entry_price:.4f}` | 수량: `{quantity}` | 레버리지: `{leverage}x`\n"
|
||
f"SL: `{stop_loss:.4f}` | TP: `{take_profit:.4f}`\n"
|
||
f"RSI: `{rsi:.2f}` | MACD Hist: `{macd:.6f}` | ATR: `{atr:.6f}`"
|
||
)
|
||
self._send(msg)
|
||
|
||
def notify_close(
|
||
self,
|
||
symbol: str,
|
||
side: str,
|
||
close_reason: str,
|
||
exit_price: float,
|
||
estimated_pnl: float,
|
||
net_pnl: float,
|
||
diff: float,
|
||
) -> None:
|
||
emoji_map = {"TP": "✅", "SL": "❌", "MANUAL": "🔶"}
|
||
emoji = emoji_map.get(close_reason, "🔶")
|
||
msg = (
|
||
f"{emoji} **[{symbol}] {side} {close_reason} 청산**\n"
|
||
f"청산가: `{exit_price:.4f}`\n"
|
||
f"예상 수익: `{estimated_pnl:+.4f} USDT`\n"
|
||
f"실제 순수익: `{net_pnl:+.4f} USDT`\n"
|
||
f"차이(슬리피지+수수료): `{diff:+.4f} USDT`"
|
||
)
|
||
self._send(msg)
|
||
|
||
def notify_info(self, message: str) -> None:
|
||
self._send(f"ℹ️ {message}")
|