Compare commits
2 Commits
dc9d2dca88
...
18babaf910
| Author | SHA1 | Date | |
|---|---|---|---|
| 18babaf910 | |||
| 5c4803b339 |
@@ -71,6 +71,10 @@ def _parse_exit_type(exit_reason: Optional[str]) -> str:
|
||||
if not exit_reason:
|
||||
return "unknown"
|
||||
reason = exit_reason.lower()
|
||||
# 시간손절(DT "시간 손절: N분 경과…", 스윙 "시간손절 …")은 손실 한도 도달이 아니라
|
||||
# 보유 시간 초과 후 조기 정리다 — "손절" 글자만 보고 stop_loss 로 묶지 않는다(2026-10-07).
|
||||
if "time_stop" in reason or "시간 손절" in reason or "시간손절" in reason:
|
||||
return "time_stop"
|
||||
if "stop_loss" in reason or "손절" in reason:
|
||||
return "stop_loss"
|
||||
if "trailing" in reason or "트레일링" in reason:
|
||||
@@ -777,10 +781,23 @@ def generate_journal_for_trade(trade: TradeHistory, perf_context: Optional[Dict]
|
||||
result_comment = "손절 처리되었습니다. 진입 조건 및 시장 국면을 재검토하세요."
|
||||
|
||||
# 청산 유형별 코멘트
|
||||
# 임계값은 하드코딩하지 않고 실효 설정값을 쓴다(CLAUDE.md §4).
|
||||
from app.core.config import settings as _s
|
||||
exit_comments = {
|
||||
"stop_loss": "→ 손절 기준(-6% 또는 ATR 기반)에 도달하여 강제 청산.",
|
||||
"trailing_stop": "→ 트레일링 스탑(고점-1.5×ATR)에 도달. 고점 대비 일정 비율 하락.",
|
||||
"take_profit": "→ 익절 기준(+2R 또는 RSI 65+)에 도달하여 수익 확정.",
|
||||
"stop_loss": (
|
||||
f"→ 손절 기준(스윙 -{abs(float(_s.STOP_LOSS_PERCENT)):g}% / 고변동 -{abs(float(_s.STOP_LOSS_HIGH_VOL)):g}%, "
|
||||
f"ATR×{float(_s.ATR_STOP_MULTIPLIER):g}, 데이트레이딩은 진입 시 손절가)에 도달하여 청산."
|
||||
),
|
||||
"time_stop": (
|
||||
f"→ 시간손절: 데이트레이딩은 진입 {int(_s.DAY_TRADE_TIME_STOP_MINUTES)}분 경과 후 수익률 "
|
||||
f"{float(_s.DAY_TRADE_TIME_STOP_MIN_GAIN) * 100:+.1f}% 미만, 스윙은 장기 보유 수익 미달로 조기 정리. "
|
||||
"손실 한도 도달이 아니다."
|
||||
),
|
||||
"trailing_stop": f"→ 트레일링 스탑(고점-{float(_s.ATR_TRAILING_MULTIPLIER):g}×ATR)에 도달. 고점 대비 일정 비율 하락.",
|
||||
"take_profit": (
|
||||
f"→ 익절 기준(RSI {float(_s.MEAN_REVERSION_RSI_EXIT_THRESHOLD):g}+ 1차 / "
|
||||
f"{float(_s.MEAN_REVERSION_RSI_FULL_EXIT):g}+ 전량, 데이트레이딩 목표가)에 도달하여 수익 확정."
|
||||
),
|
||||
"eod_forced": "→ 장 마감 강제 청산(15:20 EOD). 포지션 오버나이트 방지.",
|
||||
"rsi_exit": "→ RSI 과매수 구간 진입으로 부분/전량 청산.",
|
||||
"manual": "→ 수동 청산 처리.",
|
||||
|
||||
27
backend/tests/test_journal_time_stop_20261007.py
Normal file
27
backend/tests/test_journal_time_stop_20261007.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""시간손절이 '손절(stop_loss)'로 분류·서술되던 문제 (2026-10-07)."""
|
||||
from app.core.config import settings
|
||||
from app.services.day_trading_strategy import TIME_STOP_REASON_PREFIX
|
||||
from app.services.trade_analyzer import _parse_exit_type
|
||||
|
||||
|
||||
def test_dt_time_stop_reason_is_time_stop():
|
||||
reason = f"{TIME_STOP_REASON_PREFIX}: 32분 경과, 수익=-0.4% < {settings.DAY_TRADE_TIME_STOP_MIN_GAIN * 100:.1f}%"
|
||||
assert _parse_exit_type(reason) == "time_stop"
|
||||
|
||||
|
||||
def test_swing_time_stop_reason_is_time_stop():
|
||||
assert _parse_exit_type("시간손절: 20거래일 보유, 수익 1.2% < 3%") == "time_stop"
|
||||
|
||||
|
||||
def test_real_stop_loss_still_stop_loss():
|
||||
assert _parse_exit_type("손절: -7.2% (기준 -7%)") == "stop_loss"
|
||||
assert _parse_exit_type("stop_loss hit") == "stop_loss"
|
||||
|
||||
|
||||
def test_journal_comment_uses_settings_not_hardcoded():
|
||||
import inspect
|
||||
from app.services import trade_analyzer
|
||||
|
||||
src = inspect.getsource(trade_analyzer)
|
||||
assert "손절 기준(-6%" not in src # 옛 하드코딩 서술 제거
|
||||
assert "RSI 65+" not in src
|
||||
@@ -6,6 +6,19 @@ import {
|
||||
} from 'lucide-react'
|
||||
import api from '../lib/api'
|
||||
|
||||
/** 청산 유형 표시명 (백엔드 trade_analyzer._parse_exit_type 키) */
|
||||
const EXIT_TYPE_LABELS: Record<string, string> = {
|
||||
stop_loss: '손절',
|
||||
time_stop: '시간손절 (조기 정리)',
|
||||
trailing_stop: '트레일링 스탑',
|
||||
take_profit: '익절',
|
||||
eod_forced: '장 마감 청산',
|
||||
rsi_exit: 'RSI 청산',
|
||||
manual: '수동 청산',
|
||||
unknown: '미상',
|
||||
}
|
||||
|
||||
|
||||
// ── 타입 정의 ─────────────────────────────────────────────────────────────────
|
||||
|
||||
interface PerformanceSummary {
|
||||
@@ -287,7 +300,7 @@ export default function Analysis() {
|
||||
{Object.entries(perf.by_exit_type).map(([etype, stats]) => (
|
||||
<div key={etype} className="flex items-center justify-between py-2 border-b border-gray-700/50 last:border-0">
|
||||
<div>
|
||||
<span className="text-sm text-gray-200 font-medium">{etype}</span>
|
||||
<span className="text-sm text-gray-200 font-medium">{EXIT_TYPE_LABELS[etype] ?? etype}</span>
|
||||
<span className="ml-2 text-xs text-gray-500">{stats.count}건</span>
|
||||
</div>
|
||||
<span className={`text-sm ${stats.avg_return >= 0 ? 'text-green-400' : 'text-red-400'}`}>
|
||||
|
||||
Reference in New Issue
Block a user