feat: 트레일링 스탑 전환 + 사전 필터 강화 + 예산 증액

- cascade/LLM 매도 제거 -> 트레일링 스탑 (고점 -1.5%, 손절 -2%, 타임아웃 4h)
- 사전 필터 3종 추가: 횡보/고점/연속양봉(>=2) -> LLM 호출 57% 절감
- 현재가 매수 (LLM 가격 제안 제거)
- 종목 30개 -> 10개, BTC 제외
- 예산: 100K/3pos -> 1M/5pos (종목당 200K)
- VOL_KRW_MIN: 2M -> 5M, BUY_TIMEOUT: 60 -> 180초
- LLM 프롬프트: 연패 무시, get_trade_history 제거
- 3월 백테스트: 승률 52.1%, PNL +17,868원
- STRATEGY.md 전면 재작성

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
joungmin
2026-03-06 20:33:15 +09:00
parent 872163a3d1
commit 976c53ed66
4 changed files with 930 additions and 485 deletions

View File

@@ -103,12 +103,15 @@ def _tool_get_context(ticker: str) -> str:
{'t': ticker},
)
rows = cur.fetchall()
conn.close()
if not rows:
conn.close()
return f"{ticker} 컨텍스트 데이터 없음"
parts = []
for ctx_type, content in rows:
parts.append(f"[{ctx_type}]\n{content}")
# CLOB(LOB) → str 변환 (conn 닫기 전에 읽어야 함)
text = content.read() if hasattr(content, 'read') else str(content)
parts.append(f"[{ctx_type}]\n{text}")
conn.close()
return f"{ticker} 종목 컨텍스트:\n" + "\n\n".join(parts)
except Exception as e:
return f"DB 오류: {e}"
@@ -142,7 +145,8 @@ def _tool_get_trade_history(ticker: str, limit: int = 10) -> str:
reason = r[5] or ''
lines.append(f" {ts} {wl} {pnl:+.2f}% {reason}")
header = f"{ticker} 최근 {len(rows)}건 (승률 {wins}/{len(rows)}={wins/len(rows)*100:.0f}%):"
return header + "\n" + "\n".join(lines)
note = "\n ※ 과거 성과는 참고용입니다. 현재 시그널 강도가 높으면 과거 연패와 무관하게 진입하세요."
return header + "\n" + "\n".join(lines) + note
except Exception as e:
return f"DB 오류: {e}"
@@ -371,6 +375,41 @@ def _describe_bars(bar_list: list[dict], current_price: float) -> str:
return '\n'.join(lines) + summary
def _calc_momentum(bar_list: list[dict], current_price: float, bar_sec: int = 20) -> str:
"""다중 타임프레임 모멘텀 요약. 매도 LLM에 상승 곡선 판단 근거 제공."""
if len(bar_list) < 10:
return ''
lines = []
# 1분(3봉), 3분(9봉), 5분(15봉), 10분(30봉) 전 가격 대비 변화율
intervals = [
('1분', 3), ('3분', 9), ('5분', 15), ('10분', 30),
]
for label, n_bars in intervals:
if len(bar_list) < n_bars + 1:
continue
past_price = bar_list[-(n_bars + 1)]['close']
chg = (current_price - past_price) / past_price * 100
arrow = '' if chg > 0.3 else '' if chg < -0.3 else ''
lines.append(f' {label}전 대비: {chg:+.2f}% {arrow} ({past_price:,.0f}{current_price:,.0f})')
# 최근 15봉 연속 상승/하락 카운트
recent = bar_list[-15:]
up_count = sum(1 for b in recent if b['close'] > b['open'])
dn_count = sum(1 for b in recent if b['close'] < b['open'])
# 최근 15봉 최저가 → 현재가 상승폭
period_low = min(b['low'] for b in recent)
rise_from_low = (current_price - period_low) / period_low * 100
lines.append(f' 최근 15봉: 양봉 {up_count}개 / 음봉 {dn_count}')
lines.append(f' 구간 저점 대비: {rise_from_low:+.2f}% ({period_low:,.0f}{current_price:,.0f})')
if not lines:
return ''
return '[모멘텀 분석]\n' + '\n'.join(lines)
def _build_prompt(
ticker: str,
entry_price: float,
@@ -379,11 +418,13 @@ def _build_prompt(
current_target: float,
bar_desc: str,
market_context: str = '',
momentum_desc: str = '',
) -> str:
pnl_pct = (current_price - entry_price) / entry_price * 100
target_gap = (current_target - current_price) / current_price * 100
market_section = f'\n{market_context}\n' if market_context else ''
market_section = f'\n{market_context}\n' if market_context else ''
momentum_section = f'\n{momentum_desc}\n' if momentum_desc else ''
return f"""당신은 암호화폐 단기 트레이더입니다.
아래 포지션과 가격 흐름을 분석해 **지정가 매도 목표가**를 판단하세요.
@@ -399,11 +440,14 @@ def _build_prompt(
{market_section}
[최근 {INPUT_BARS}봉 (20초봉)]
{bar_desc}
{momentum_section}
[운용 정책 참고 — 최종 판단은 당신이 결정]
- 단기 거래량 가속 신호 진입 후 cascade 청산 전략 (지정가 단계적 조정)
- 수익 목표: 진입가 대비 +0.5% ~ +2% 구간
- 손절 기준: 진입가 대비 -2% 이하이면 즉시 시장가 매도를 강력 권고 (action=sell, price=현재가)
- 체결 가능성이 낮으면 현실적인 목표가로 조정 권장
- **모멘텀이 강하면(1분~10분 전 대비 계속 상승 중) 성급하게 팔지 말고 hold하세요**
- **양봉 비율이 높고 구간 저점 대비 상승폭이 크면 추세가 살아있는 것입니다**
- 상승 여력이 있으면 hold 권장
반드시 아래 JSON 형식으로만 응답하세요. 설명이나 다른 텍스트를 절대 포함하지 마세요.
@@ -543,12 +587,11 @@ def get_entry_price(
market_section = f'\n{mkt_ctx}\n' if mkt_ctx else ''
prompt = f"""당신은 암호화폐 단기 트레이더입니다.
아래 시그널을 분석해 **매수 여부와 지정가 매수 가격** 판단하세요.
아래 시그널을 분석해 **매수 여부** 판단하세요. (매수 가격은 현재가로 자동 설정됩니다)
반드시 제공된 DB tool을 호출해 추가 데이터를 조회하세요:
- get_btc_trend: BTC 추세 확인 (필수 — BTC 락 시 알트 매수 위험)
- get_btc_trend: BTC 추세 확인 (필수 — BTC 락 시 알트 매수 위험)
- get_ticker_context: 종목 24h/7d 변동, 뉴스 확인
- get_trade_history: 이 종목 과거 거래 성과 확인
- get_ohlcv: 1분봉으로 지지/저항선 확인
- get_ohlcv: 1분봉으로 현재 추세 확인
[시그널 감지]
종목 : {ticker}
@@ -561,16 +604,16 @@ F&G지수: {fng} ({'공포' if fng <= 40 else '중립' if fng <= 50 else '탐욕
{bar_desc}
[판단 기준]
- 거래량 급증이 진짜 매집 신호인지, 일시적 노이즈인지 구분
- BTC가 락 중이면 알트코인 매수 자제
- 최근 이 종목에서 연패 중이면 신중하게
- 현재가보다 약간 낮은 지정가를 설정해 유리한 가격에 매수
- 상승 추세가 이미 많이 진행됐으면 진입 자제
- 거래량 급증 시그널이 왔으면 빠르게 올라타는 것이 핵심
- BTC가 락 중(-2% 이상)이면 자제하되, 횡보/소폭하락은 진입 OK
- 상승 추세가 이미 많이 진행됐으면(+5% 이상) 진입 자제
- **과거 연패/승률은 절대 고려하지 마세요. 현재 시그널만 보고 판단하세요.**
- **get_trade_history를 호출하지 마세요. 과거 거래 이력은 판단에 불필요합니다.**
반드시 아래 JSON 형식으로만 응답하세요. 설명이나 다른 텍스트를 절대 포함하지 마세요.
매수할 경우:
{{"action": "buy", "price": 숫자, "confidence": "high|medium|low", "reason": "판단 근거 한줄 요약", "market_status": "상승|하락|횡보|급등|급락"}}
{{"action": "buy", "confidence": "high|medium|low", "reason": "판단 근거 한줄 요약", "market_status": "상승|하락|횡보|급등|급락"}}
매수하지 않을 경우:
{{"action": "skip", "reason": "매수하지 않는 이유 한줄 요약", "market_status": "상승|하락|횡보|급등|급락"}}"""
@@ -621,12 +664,14 @@ def get_exit_price(
elapsed_min = (datetime.now() - pos['entry_ts']).total_seconds() / 60
current_target = pos.get('sell_price') or entry_price * 1.005
bar_desc = _describe_bars(bar_list, current_price)
mkt_ctx = _get_market_context(ticker)
bar_desc = _describe_bars(bar_list, current_price)
mkt_ctx = _get_market_context(ticker)
momentum_desc = _calc_momentum(bar_list, current_price)
prompt = _build_prompt(
ticker, entry_price, current_price,
elapsed_min, current_target, bar_desc,
market_context=mkt_ctx,
momentum_desc=momentum_desc,
)
data = _call_llm(prompt, ticker)