feat(sim): 물타기(애버리징) 토글 — 하락 시 손절 대신 평단↓ 추가매수
- config: AVERAGING_ENTRY·AVERAGING_DROP_PCT(0.10)·CRASH_DROP_PCT(0.10) - signals.evaluate_holding: market_ok 인자 + 물타기 분기 (직전매수가 -10% 하락 시 ADD-down, 한도/강세장/비급락 조건, 당일 -10%급락은 손절·약세장 금지) - engine·backtest: add_kind='down'이면 손절선 낮은 평단 기준 재설정(up은 위로 래칫 유지), engine은 market_ok 전달 - 첫 매수는 기존 검증 진입 그대로(우량주만 물타기) - 백테스트: 즉시매수와 반대로 유리(10/20/60 수익·승률↑) → 라이브 변이 3개(v301/302/303) 등록, 총 223 - 웹 '물타기' 칩 + 설명 토스트 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -866,6 +866,19 @@ def _immediate_chip_html(p) -> str:
|
||||
f'data-detail="{_esc(desc)}">즉시</span>')
|
||||
|
||||
|
||||
def _avg_chip_html(p) -> str:
|
||||
"""물타기(애버리징) 칩 — 보유 중 하락 시 손절 대신 추가매수로 평단 낮추는 변이. 아니면 빈 문자열."""
|
||||
if not int((p or {}).get('AVERAGING_ENTRY', 0)):
|
||||
return ''
|
||||
drop = (p or {}).get('AVERAGING_DROP_PCT', 0.10)
|
||||
desc = ('물타기(애버리징) 변이 — 우량주(추세·정배열 통과)를 산 뒤 직전 매수가보다 '
|
||||
f'-{drop*100:.0f}% 떨어지면, 손절하지 않고 추가매수해 평균단가를 낮춰요(종목당 10% 한도). '
|
||||
'단 ①당일 -10%↓ 급락이면 물타기 말고 손절 ②약세장이면 물타기 금지 — 떨어지는 칼·하락장 물타기는 피해요. '
|
||||
'(손절형 기존 전략과 라이브 비교 중)')
|
||||
return (f'<span class="bic bic-avg" data-title="물타기(애버리징)" '
|
||||
f'data-detail="{_esc(desc)}">물타기</span>')
|
||||
|
||||
|
||||
def _backtest_panel() -> str:
|
||||
"""백테스트 결과 — 유형별(세부 라벨) 대표(1위) 조합 요약. 비교군은 이 대표들로 자동 구성되므로
|
||||
수동 추가/제거 없이 읽기 전용 요약만 보여준다."""
|
||||
@@ -1123,7 +1136,7 @@ def _compare_panel() -> str:
|
||||
rows += (
|
||||
f'<details class="row" data-reg="{i}" data-rank="{rank_of[v["id"]]}" data-bt="{bt_attr}" '
|
||||
f'data-ret="{ret}" data-held="{s.get("open_positions", 0)}" data-trades="{s.get("closed_trades", 0)}"><summary>'
|
||||
f'<span class="vid">{rank_of[v["id"]]}위</span>{_bear_chip_html(mp)}{_relax_chip_html(mp)}{_immediate_chip_html(mp)} <span class="muted">{_vid_disp(vid)}</span> '
|
||||
f'<span class="vid">{rank_of[v["id"]]}위</span>{_bear_chip_html(mp)}{_relax_chip_html(mp)}{_immediate_chip_html(mp)}{_avg_chip_html(mp)} <span class="muted">{_vid_disp(vid)}</span> '
|
||||
f'<span class="nm">{_esc(_cat_name(mp))}</span>{tag}'
|
||||
f'<span class="rt"><span class="muted">🛒{s.get("open_positions", 0)}</span> '
|
||||
f'<b style="color:{rc}">{ret:+g}%</b></span></summary>'
|
||||
@@ -1310,7 +1323,7 @@ h1{font-size:17px;margin:10px 0 2px}h2{font-size:14px;margin:22px 0 8px;color:#c
|
||||
.vid{background:#1e293b;color:#7dd3fc;border-radius:5px;padding:1px 6px;font-size:11px;font-weight:700}
|
||||
.bic{border-radius:5px;padding:1px 5px;font-size:10px;font-weight:700;margin-left:4px}
|
||||
.bic0{background:#334155;color:#cbd5e1}.bic1{background:#1e3a5f;color:#7dd3fc}.bic2{background:#4a2e12;color:#fbbf24}
|
||||
.bic-relax{background:#3b1d4a;color:#d8b4fe}.bic-now{background:#0c3a2e;color:#6ee7b7}
|
||||
.bic-relax{background:#3b1d4a;color:#d8b4fe}.bic-now{background:#0c3a2e;color:#6ee7b7}.bic-avg{background:#3a2a0c;color:#fcd34d}
|
||||
.cmp-btlink{background:#0f2a3f;color:#7dd3fc;border:1px solid #1e3a5f;border-radius:6px;padding:3px 8px;font-size:11px;font-weight:600;display:inline-block}
|
||||
.note{white-space:pre-wrap;line-height:1.65;font-size:13px;color:#cbd5e1}
|
||||
ul.an{margin:6px 0;padding-left:18px;line-height:1.7;font-size:13px}
|
||||
@@ -1441,7 +1454,7 @@ td:first-child,th:first-child{text-align:left}
|
||||
<title>{_esc(vid)} 전략 상세</title>{style}</head><body><div id="page">
|
||||
{back}
|
||||
<h1><span class="vid">{rank or "—"}위</span> <span class="muted">{_vid_disp(vid)}</span> {_esc(_cat_name(params))}
|
||||
<span class="bic bic{bm}">{_BEAR_WORD[bm]}</span>{_relax_chip_html(params)}{_immediate_chip_html(params)} {icon}</h1>
|
||||
<span class="bic bic{bm}">{_BEAR_WORD[bm]}</span>{_relax_chip_html(params)}{_immediate_chip_html(params)}{_avg_chip_html(params)} {icon}</h1>
|
||||
<div class="sub">{_esc(_market_fit_summary(params))} · 약세장대처: {_BEAR_NAME[bm]}</div>
|
||||
|
||||
<div class="card">{notes_html}</div>
|
||||
@@ -2010,8 +2023,9 @@ def render_html(view: str | None = None, day_date: str | None = None,
|
||||
_vp = vv.get('params') or {}
|
||||
_rx = ' 완화' if (not int(_vp.get('TREND_SLOPE_GATE', 1)) or not int(_vp.get('MID_ARRAY_GATE', 1))) else ''
|
||||
_im = ' 즉시' if int(_vp.get('IMMEDIATE_ENTRY', 0)) else ''
|
||||
_av = ' 물타기' if int(_vp.get('AVERAGING_ENTRY', 0)) else ''
|
||||
opts += (f'<option value="{_esc(vv["id"])}"{_sel}>{_fav}{_rk(vv["id"])}{_vid_disp(vv["id"])} '
|
||||
f'[{_BEAR_WORD[_bear_mode(_vp)]}{_rx}{_im}] · {_esc(_cat_name(_vp))}</option>')
|
||||
f'[{_BEAR_WORD[_bear_mode(_vp)]}{_rx}{_im}{_av}] · {_esc(_cat_name(_vp))}</option>')
|
||||
view_sel = (f'<div class="sub" style="margin:4px 0 8px">체크리스트 시각: '
|
||||
f'<select id="judge-view" '
|
||||
f'onchange="location.href=\'/?t=watch&view=\'+encodeURIComponent(this.value)">{opts}</select></div>')
|
||||
@@ -2131,6 +2145,7 @@ h3.grp{{font-size:13px;margin:16px 0 8px;color:#cbd5e1}}
|
||||
.bic2{{background:#4a2e12;color:#fbbf24}}
|
||||
.bic-relax{{background:#3b1d4a;color:#d8b4fe}}
|
||||
.bic-now{{background:#0c3a2e;color:#6ee7b7}}
|
||||
.bic-avg{{background:#3a2a0c;color:#fcd34d}}
|
||||
.cmp-sortbar{{display:flex;gap:6px;margin:0 0 10px}}
|
||||
.cmp-sortbar button{{flex:1;background:#141b24;border:1px solid #1f2937;border-radius:8px;color:#9ca3af;font-size:11px;font-weight:600;padding:7px 4px;cursor:pointer}}
|
||||
.cmp-sortbar button.active{{background:#15406e;color:#fff;border-color:#2952cc}}
|
||||
|
||||
Reference in New Issue
Block a user