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:
hyowons
2026-06-16 14:30:07 +09:00
parent 70f6b9a34d
commit eaa276b26d
233 changed files with 397 additions and 233 deletions
+5 -2
View File
@@ -286,7 +286,8 @@ def _run_scan(pf, uni, quotes, cmkt, now, execute: bool = True) -> dict:
flow = data.flow_net(data.investor_flow(code))
anl = data.analyst(code, cur_price)
dec = signals.evaluate_holding(pf.positions[code], ind, flow, anl,
held_days=_held_days(pf.positions[code], now))
held_days=_held_days(pf.positions[code], now),
market_ok=mkt_ok.get(cmkt.get(code, ''), True))
pf.apply_position_update(code, dec['position_update'])
dec.update({k: pf.positions[code].get(k) for k in
('entry_price', 'qty', 'stop', 'target', 'trailing_on', 'entry_at')})
@@ -305,7 +306,9 @@ def _run_scan(pf, uni, quotes, cmkt, now, execute: bool = True) -> dict:
# 새 평단 기준 손절·목표 재산정 (손절은 위로만 래칫)
new_entry = pf.positions[code]['entry_price']
nstop, ntarget = signals.compute_stop_target(new_entry, ind['atr'], ind['recent_low'])
pf.positions[code]['stop'] = max(pf.positions[code]['stop'], nstop)
# 추격매수(up)=손절 위로만 래칫 / 물타기(down)=낮아진 평단 기준으로 손절 재설정
pf.positions[code]['stop'] = (nstop if dec.get('add_kind') == 'down'
else max(pf.positions[code]['stop'], nstop))
pf.positions[code]['target'] = ntarget
buys.append(rec)
dec.update({'fill_price': fill, 'entry_price': new_entry,