feat: add market regime filter and compound reinvestment
- Add market_regime.py: BTC/ETH/SOL/XRP weighted 2h trend score Bull(≥+1.5%) / Neutral / Bear(<-1%) regime detection with 10min cache - strategy.py: dynamic TREND/VOL thresholds based on current regime Bull: 3%/1.5x, Neutral: 5%/2.0x, Bear: 8%/3.5x - price_collector.py: always include leader coins in price history - trader.py: compound reinvestment (profit added to budget, floor at initial) - notify.py: regime info in hourly report, P&L icons (✅/❌, 💚/🔴) - main.py: hourly status at top-of-hour, filter positions held 1h+ - backtest.py: timestop/combo comparison modes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -28,21 +28,36 @@ def _send(text: str) -> None:
|
||||
logger.error(f"Telegram 알림 실패: {e}")
|
||||
|
||||
|
||||
def notify_buy(ticker: str, price: float, amount: float, invested_krw: int) -> None:
|
||||
def notify_buy(
|
||||
ticker: str, price: float, amount: float, invested_krw: int,
|
||||
max_budget: int = 0, per_position: int = 0,
|
||||
) -> None:
|
||||
budget_line = (
|
||||
f"운용예산: {max_budget:,}원 (포지션당 {per_position:,}원)\n"
|
||||
if max_budget else ""
|
||||
)
|
||||
_send(
|
||||
f"📈 <b>[매수]</b> {ticker}\n"
|
||||
f"가격: {price:,.0f}원\n"
|
||||
f"수량: {amount}\n"
|
||||
f"투자금: {invested_krw:,}원"
|
||||
f"투자금: {invested_krw:,}원\n"
|
||||
f"{budget_line}"
|
||||
)
|
||||
|
||||
|
||||
def notify_sell(ticker: str, price: float, pnl_pct: float, reason: str) -> None:
|
||||
emoji = "✅" if pnl_pct >= 0 else "🔴"
|
||||
def notify_sell(
|
||||
ticker: str, price: float, pnl_pct: float, reason: str,
|
||||
krw_profit: float = 0.0, fee_krw: float = 0.0,
|
||||
cum_profit: float = 0.0,
|
||||
) -> None:
|
||||
trade_emoji = "✅" if pnl_pct >= 0 else "❌"
|
||||
cum_emoji = "💚" if cum_profit >= 0 else "🔴"
|
||||
_send(
|
||||
f"{emoji} <b>[매도]</b> {ticker}\n"
|
||||
f"{trade_emoji} <b>[매도]</b> {ticker}\n"
|
||||
f"가격: {price:,.0f}원\n"
|
||||
f"수익률: {pnl_pct:+.1f}%\n"
|
||||
f"수익률: {pnl_pct:+.2f}%\n"
|
||||
f"실손익: {krw_profit:+,.0f}원 (수수료 {fee_krw:,.0f}원)\n"
|
||||
f"{cum_emoji} 누적손익: {cum_profit:+,.0f}원\n"
|
||||
f"사유: {reason}"
|
||||
)
|
||||
|
||||
@@ -51,19 +66,50 @@ def notify_error(message: str) -> None:
|
||||
_send(f"⚠️ <b>[오류]</b>\n{message}")
|
||||
|
||||
|
||||
def notify_status(positions: dict) -> None:
|
||||
"""1시간마다 포지션 현황 요약 전송."""
|
||||
def notify_status(
|
||||
positions: dict,
|
||||
max_budget: int = 0,
|
||||
per_position: int = 0,
|
||||
cum_profit: float = 0.0,
|
||||
) -> None:
|
||||
"""정각마다 시장 레짐 + 1시간 이상 보유 포지션 현황 전송."""
|
||||
from datetime import datetime
|
||||
import pyupbit
|
||||
from .market_regime import get_regime
|
||||
|
||||
now = datetime.now().strftime("%H:%M")
|
||||
cum_sign = "+" if cum_profit >= 0 else ""
|
||||
|
||||
if not positions:
|
||||
_send(f"📊 <b>[{now} 현황]</b>\n보유 포지션 없음 — 매수 신호 대기 중")
|
||||
# 시장 레짐
|
||||
regime = get_regime()
|
||||
regime_line = (
|
||||
f"{regime['emoji']} 시장: {regime['name'].upper()} "
|
||||
f"(score {regime['score']:+.2f}%) "
|
||||
f"| 조건 TREND≥{regime['trend_pct']}% / VOL≥{regime['vol_mult']}x\n"
|
||||
)
|
||||
|
||||
# 1시간 이상 보유 포지션만 필터
|
||||
long_positions = {
|
||||
ticker: pos for ticker, pos in positions.items()
|
||||
if (datetime.now() - pos["entry_time"]).total_seconds() >= 3600
|
||||
}
|
||||
|
||||
cum_emoji = "💚" if cum_profit >= 0 else "🔴"
|
||||
budget_info = (
|
||||
f"💰 운용예산: {max_budget:,}원 | 포지션당: {per_position:,}원\n"
|
||||
f"{cum_emoji} 누적손익: {cum_sign}{cum_profit:,.0f}원\n"
|
||||
if max_budget else ""
|
||||
)
|
||||
|
||||
# 포지션 없어도 레짐 정보는 전송
|
||||
header = f"📊 <b>[{now} 현황]</b>\n{regime_line}{budget_info}"
|
||||
|
||||
if not long_positions:
|
||||
_send(header + "1h+ 보유 포지션 없음")
|
||||
return
|
||||
|
||||
lines = [f"📊 <b>[{now} 현황]</b>"]
|
||||
for ticker, pos in positions.items():
|
||||
lines = [header]
|
||||
for ticker, pos in long_positions.items():
|
||||
current = pyupbit.get_current_price(ticker)
|
||||
if not current:
|
||||
continue
|
||||
@@ -73,9 +119,9 @@ def notify_status(positions: dict) -> None:
|
||||
elapsed = (datetime.now() - pos["entry_time"]).total_seconds() / 3600
|
||||
emoji = "📈" if pnl >= 0 else "📉"
|
||||
lines.append(
|
||||
f"\n{emoji} <b>{ticker}</b>\n"
|
||||
f"{emoji} <b>{ticker}</b>\n"
|
||||
f" 현재가: {current:,.0f}원\n"
|
||||
f" 수익률: {pnl:+.1f}%\n"
|
||||
f" 수익률: {pnl:+.2f}%\n"
|
||||
f" 최고가 대비: -{drop:.1f}%\n"
|
||||
f" 보유: {elapsed:.1f}h"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user