전략수정 및 버그수정
This commit is contained in:
@@ -16,7 +16,9 @@
|
|||||||
"Bash(docker restart:*)",
|
"Bash(docker restart:*)",
|
||||||
"mcp__auto-stock__get_recommendations",
|
"mcp__auto-stock__get_recommendations",
|
||||||
"Bash(pip install:*)",
|
"Bash(pip install:*)",
|
||||||
"Bash(python -c:*)"
|
"Bash(python -c:*)",
|
||||||
|
"Bash(git pull:*)",
|
||||||
|
"Bash(git stash:*)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"enableAllProjectMcpServers": true,
|
"enableAllProjectMcpServers": true,
|
||||||
|
|||||||
@@ -115,13 +115,13 @@ def aggregate_to_5min(candles: List[MinuteCandle]) -> List[MinuteCandle]:
|
|||||||
if not candles:
|
if not candles:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
buckets: Dict[int, List[MinuteCandle]] = {}
|
buckets: Dict[tuple, List[MinuteCandle]] = {}
|
||||||
for c in candles:
|
for c in candles:
|
||||||
if not c.time:
|
if not c.time:
|
||||||
continue
|
continue
|
||||||
h = int(c.time[:2])
|
h = int(c.time[:2])
|
||||||
m = int(c.time[2:4])
|
m = int(c.time[2:4])
|
||||||
key = (h * 60 + m) // 5
|
key = (c.date, (h * 60 + m) // 5) # 날짜 포함 → 다른 날 캔들 혼합 방지
|
||||||
buckets.setdefault(key, []).append(c)
|
buckets.setdefault(key, []).append(c)
|
||||||
|
|
||||||
result: List[MinuteCandle] = []
|
result: List[MinuteCandle] = []
|
||||||
@@ -234,9 +234,11 @@ def check_orb_signal(
|
|||||||
if recent_vol < orb.avg_vol_per_min * 1.5:
|
if recent_vol < orb.avg_vol_per_min * 1.5:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# 직전 5분봉 양봉 확인
|
# 직전 5분봉 양봉 확인 (5분봉 2개 이상 없으면 보수적으로 차단)
|
||||||
candles_5m = aggregate_to_5min(candles_1min)
|
candles_5m = aggregate_to_5min(candles_1min)
|
||||||
if len(candles_5m) >= 2 and candles_5m[-2].close < candles_5m[-2].open:
|
if len(candles_5m) < 2:
|
||||||
|
return None # 데이터 부족 시 진입 차단
|
||||||
|
if candles_5m[-2].close < candles_5m[-2].open:
|
||||||
return None # 직전 5분봉 음봉 → 스킵
|
return None # 직전 5분봉 음봉 → 스킵
|
||||||
|
|
||||||
stop_loss = max(orb.low, current_price * (1 - STOP_LOSS_PCT))
|
stop_loss = max(orb.low, current_price * (1 - STOP_LOSS_PCT))
|
||||||
@@ -298,7 +300,8 @@ def check_vwap_bounce_signal(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
atr = _atr(candles_5min, 14)
|
atr = _atr(candles_5min, 14)
|
||||||
stop_loss = (current_price - 1.5 * atr) if atr else current_price * (1 - STOP_LOSS_PCT)
|
raw_stop = (current_price - 1.5 * atr) if atr else current_price * (1 - STOP_LOSS_PCT)
|
||||||
|
stop_loss = max(raw_stop, current_price * (1 - STOP_LOSS_PCT * 2), 1.0)
|
||||||
target = (current_price + 2.0 * atr) if atr else vwap * 1.03
|
target = (current_price + 2.0 * atr) if atr else vwap * 1.03
|
||||||
|
|
||||||
return DayTradeSignal(
|
return DayTradeSignal(
|
||||||
@@ -355,8 +358,13 @@ def check_rsi_bb_signal(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
atr = _atr(candles_5min, 14)
|
atr = _atr(candles_5min, 14)
|
||||||
stop_loss = (current_price - 1.5 * atr) if atr else current_price * (1 - STOP_LOSS_PCT)
|
raw_stop = (current_price - 1.5 * atr) if atr else current_price * (1 - STOP_LOSS_PCT)
|
||||||
target = bb_middle # BB 중앙선까지 반등 목표
|
stop_loss = max(raw_stop, current_price * (1 - STOP_LOSS_PCT * 2), 1.0)
|
||||||
|
# 최소 1.5R 보장: bb_middle이 너무 가까우면 R:R 확보를 위해 목표 상향
|
||||||
|
risk = current_price - stop_loss
|
||||||
|
target = max(bb_middle, current_price + risk * 1.5)
|
||||||
|
if target <= current_price: # 방어 로직 (stop_loss ≥ current_price 극단 케이스)
|
||||||
|
return None
|
||||||
|
|
||||||
return DayTradeSignal(
|
return DayTradeSignal(
|
||||||
stock_code=stock_code,
|
stock_code=stock_code,
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ def check_buy_signal(
|
|||||||
if regime == MarketRegime.BULL:
|
if regime == MarketRegime.BULL:
|
||||||
rsi_ok = rsi <= 30 # ← 25 → 30으로 완화: 진입 기회 ~2배 증가
|
rsi_ok = rsi <= 30 # ← 25 → 30으로 완화: 진입 기회 ~2배 증가
|
||||||
vol_ok = current_volume >= avg_volume_20d * 2 # 거래량 20일평균 200% 이상
|
vol_ok = current_volume >= avg_volume_20d * 2 # 거래량 20일평균 200% 이상
|
||||||
if rsi_ok or vol_ok:
|
if rsi_ok or (vol_ok and rsi <= 50): # vol_ok 단독 시 RSI 50 상한 (과열 진입 차단)
|
||||||
bb_low_minus = bb_lower * 0.97 # -3%
|
bb_low_minus = bb_lower * 0.97 # -3%
|
||||||
bb_low_plus = bb_lower * 1.015 # +1.5%
|
bb_low_plus = bb_lower * 1.015 # +1.5%
|
||||||
if bb_low_minus <= close <= bb_low_plus:
|
if bb_low_minus <= close <= bb_low_plus:
|
||||||
@@ -299,16 +299,17 @@ def check_exit_signal(
|
|||||||
# ── 3. 2차 익절: 1차 완료 후 ─────────────────────────────────────
|
# ── 3. 2차 익절: 1차 완료 후 ─────────────────────────────────────
|
||||||
if remaining_pct <= threshold:
|
if remaining_pct <= threshold:
|
||||||
|
|
||||||
# 3a. 트레일링 스탑: 취고가 대비 -3% 하락 시 잔여 전량 청산
|
# 3a. 트레일링 스탑: 최고가 대비 -3% 하락 시 잔여 전량 청산
|
||||||
# 수익 구존 보장 위해 진입가 위 구간에서만 적용
|
# close > entry_price 조건 제거 → dead zone 차단
|
||||||
|
# (entry 아래로 떨어져도 trailing_high가 entry보다 높으면 즉시 청산)
|
||||||
if trailing_high is not None and trailing_high > entry_price:
|
if trailing_high is not None and trailing_high > entry_price:
|
||||||
trail_stop = trailing_high * 0.97
|
trail_stop = trailing_high * 0.97
|
||||||
if close < trail_stop and close > entry_price:
|
if close < trail_stop:
|
||||||
return ExitSignal(
|
return ExitSignal(
|
||||||
action="SELL_ALL",
|
action="SELL_ALL",
|
||||||
reason=(
|
reason=(
|
||||||
f"트레일링 청산: close={close:.0f} < 취고가×0.97={trail_stop:.0f} "
|
f"트레일링 청산: close={close:.0f} < 최고가×0.97={trail_stop:.0f} "
|
||||||
f"(취고가={trailing_high:.0f})"
|
f"(최고가={trailing_high:.0f})"
|
||||||
),
|
),
|
||||||
sell_pct=1.0,
|
sell_pct=1.0,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user