레디스 및 구조 수정중
This commit is contained in:
@@ -11,34 +11,43 @@
|
||||
- 일일 손실 한도: 전체 자본 -3% 초과 시 신규 진입 중단
|
||||
- 동시 최대 3종목, 종목당 최대 10% 자본
|
||||
- 전체 데이트레이딩 자본: 총자산의 최대 25% (CLAUDE.md 8.2)
|
||||
- 수수료+슬리피지 포함 기준: 0.5% (매수 0.015% + 매도 0.3% + 슬리피지 0.2%)
|
||||
|
||||
[.env 설정]
|
||||
DAY_TRADE_MAX_CAPITAL_RATIO
|
||||
DAY_TRADE_MAX_RATIO_DT
|
||||
DAY_TRADE_MAX_POSITIONS (== MAX_DAY_TRADE_POSITIONS)
|
||||
DAY_TRADE_DAILY_LOSS_LIMIT
|
||||
DAY_TRADE_ORB_STOP_LOSS_PCT
|
||||
DAY_TRADE_REVERSION_STOP_LOSS_PCT
|
||||
DAY_TRADE_TRAILING_TRIGGER
|
||||
DAY_TRADE_TRAILING_ATR_MULT
|
||||
DAY_TRADE_ORB_BREAKOUT_MARGIN
|
||||
DAY_TRADE_COST_PCT
|
||||
DAY_TRADE_ENTRY_CUTOFF_HOUR, DAY_TRADE_ENTRY_CUTOFF_MIN
|
||||
DAY_TRADE_EOD_HOUR, DAY_TRADE_EOD_MIN
|
||||
"""
|
||||
import logging
|
||||
from typing import Optional, Dict, List, Tuple
|
||||
from dataclasses import dataclass
|
||||
from datetime import time
|
||||
from app.core.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ── 장 시간 상수 (KST) ──────────────────────────────────────────────────────
|
||||
MARKET_OPEN = time(9, 0)
|
||||
ORB_END = time(9, 30) # Opening Range 집계 종료
|
||||
ENTRY_CUTOFF = time(15, 0) # 신규 진입 마감 (이후는 청산만)
|
||||
EOD_FLAT = time(15, 20) # EOD 강제 청산 시작
|
||||
MARKET_CLOSE = time(15, 30)
|
||||
|
||||
# ── 리스크 파라미터 ───────────────────────────────────────────────────────────
|
||||
MAX_CAPITAL_RATIO = 0.10 # 종목당 최대 10%
|
||||
MAX_DAY_TRADE_RATIO = 0.25 # 전체 데이트레이딩 최대 25%
|
||||
MAX_POSITIONS = 3 # 동시 최대 3종목
|
||||
DAILY_LOSS_LIMIT = -0.03 # 일일 손실 한도 -3%
|
||||
STOP_LOSS_PCT = 0.02 # ORB 손절 -2%
|
||||
STOP_LOSS_PCT_REVERSION = 0.025 # VWAP/RSI_BB 평균회귀 전략 손절 -2.5%
|
||||
# ↑ ORB(-2%)보다 약간 넓게 잡되, 일일 손실 한도(-3%) 이내에서 단일 포지션이 소화 가능하도록 설정
|
||||
TRAILING_TRIGGER = 0.015 # 트레일링 발동 +1.5%
|
||||
TRAILING_ATR_MULT = 1.0 # 트레일링 ATR 배수
|
||||
ORB_BREAKOUT_MARGIN = 0.002 # ORB 돌파 최소 여유 +0.2%
|
||||
COST_PCT = 0.005 # 수수료+슬리피지 합산 (매수+매도)
|
||||
# 동적 시간 설정 (settings에서 읽음)
|
||||
def _get_entry_cutoff() -> time:
|
||||
"""신규 진입 마감 시간 (.env: DAY_TRADE_ENTRY_CUTOFF_HOUR, DAY_TRADE_ENTRY_CUTOFF_MIN)"""
|
||||
return time(settings.DAY_TRADE_ENTRY_CUTOFF_HOUR, settings.DAY_TRADE_ENTRY_CUTOFF_MIN)
|
||||
|
||||
def _get_eod_flat() -> time:
|
||||
"""EOD 강제 청산 시작 시간 (.env: DAY_TRADE_EOD_HOUR, DAY_TRADE_EOD_MIN)"""
|
||||
return time(settings.DAY_TRADE_EOD_HOUR, settings.DAY_TRADE_EOD_MIN)
|
||||
|
||||
MARKET_CLOSE = time(15, 30)
|
||||
|
||||
|
||||
# ── 데이터 클래스 ─────────────────────────────────────────────────────────────
|
||||
@@ -226,7 +235,7 @@ def check_orb_signal(
|
||||
if not orb or current_price <= 0:
|
||||
return None
|
||||
|
||||
if current_price < orb.high * (1 + ORB_BREAKOUT_MARGIN):
|
||||
if current_price < orb.high * (1 + settings.DAY_TRADE_ORB_BREAKOUT_MARGIN):
|
||||
return None
|
||||
|
||||
# 최근 1분봉 거래량 체크
|
||||
@@ -243,7 +252,7 @@ def check_orb_signal(
|
||||
if candles_5m[-2].close < candles_5m[-2].open:
|
||||
return None # 직전 5분봉 음봉 → 스킵
|
||||
|
||||
stop_loss = max(orb.low, current_price * (1 - STOP_LOSS_PCT))
|
||||
stop_loss = max(orb.low, current_price * (1 - settings.DAY_TRADE_ORB_STOP_LOSS_PCT))
|
||||
risk = current_price - stop_loss
|
||||
# 수수료 고려: 목표 = 진입가 + 2R + 수수료 보전
|
||||
target = current_price + max(risk * 2.0, current_price * 0.03)
|
||||
@@ -302,8 +311,8 @@ def check_vwap_bounce_signal(
|
||||
return None
|
||||
|
||||
atr = _atr(candles_5min, 14)
|
||||
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_REVERSION), 1.0) # 하드 스탑 -2.5% (일일 손실 한도 -3% 이내)
|
||||
raw_stop = (current_price - 1.5 * atr) if atr else current_price * (1 - settings.DAY_TRADE_ORB_STOP_LOSS_PCT)
|
||||
stop_loss = max(raw_stop, current_price * (1 - settings.DAY_TRADE_REVERSION_STOP_LOSS_PCT), 1.0) # 하드 스탑 -2.5% (일일 손실 한도 -3% 이내)
|
||||
target = (current_price + 2.0 * atr) if atr else vwap * 1.03
|
||||
|
||||
return DayTradeSignal(
|
||||
@@ -360,8 +369,8 @@ def check_rsi_bb_signal(
|
||||
return None
|
||||
|
||||
atr = _atr(candles_5min, 14)
|
||||
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_REVERSION), 1.0) # 하드 스탑 -2.5% (일일 손실 한도 -3% 이내)
|
||||
raw_stop = (current_price - 1.5 * atr) if atr else current_price * (1 - settings.DAY_TRADE_ORB_STOP_LOSS_PCT)
|
||||
stop_loss = max(raw_stop, current_price * (1 - settings.DAY_TRADE_REVERSION_STOP_LOSS_PCT), 1.0) # 하드 스탑 -2.5% (일일 손실 한도 -3% 이내)
|
||||
# 최소 1.5R 보장: bb_middle이 너무 가까우면 R:R 확보를 위해 목표 상향
|
||||
risk = current_price - stop_loss
|
||||
target = max(bb_middle, current_price + risk * 1.5)
|
||||
@@ -403,10 +412,10 @@ def check_day_trade_exit(
|
||||
5. 5분봉 RSI ≥ 70 (과매수 청산)
|
||||
"""
|
||||
# 1. EOD 강제 청산
|
||||
if current_time >= EOD_FLAT:
|
||||
if current_time >= _get_eod_flat():
|
||||
return DayTradeExit(
|
||||
action="SELL_ALL",
|
||||
reason=f"EOD 강제 청산 ({current_time.strftime('%H:%M')} ≥ 15:20)",
|
||||
reason=f"EOD 강제 청산 ({current_time.strftime('%H:%M')} ≥ {_get_eod_flat().strftime('%H:%M')})",
|
||||
)
|
||||
|
||||
# 2. 손절
|
||||
@@ -427,10 +436,10 @@ def check_day_trade_exit(
|
||||
|
||||
# 4. 트레일링 스탑 (1.5% 이상 수익 시 발동)
|
||||
profit_pct = (highest_since_entry - entry_price) / entry_price
|
||||
if profit_pct >= TRAILING_TRIGGER:
|
||||
if profit_pct >= settings.DAY_TRADE_TRAILING_TRIGGER:
|
||||
atr = _atr(candles_5min, 14)
|
||||
if atr:
|
||||
trailing_stop = highest_since_entry - TRAILING_ATR_MULT * atr
|
||||
trailing_stop = highest_since_entry - settings.DAY_TRADE_TRAILING_ATR_MULT * atr
|
||||
else:
|
||||
trailing_stop = highest_since_entry * (1 - 0.02) # fallback: 최고가 -2% (5분봉 데이터 부족 시)
|
||||
if current_price <= trailing_stop:
|
||||
@@ -446,7 +455,7 @@ def check_day_trade_exit(
|
||||
rsi = _rsi(candles_5min, 14)
|
||||
if rsi is not None and rsi >= 70:
|
||||
gain_pct = (current_price - entry_price) / entry_price * 100
|
||||
if gain_pct > COST_PCT * 100: # 수수료 이상 수익인 경우만
|
||||
if gain_pct > settings.DAY_TRADE_COST_PCT * 100: # 수수료 이상 수익인 경우만
|
||||
return DayTradeExit(
|
||||
action="SELL_ALL",
|
||||
reason=f"과매수 청산: RSI={rsi:.1f}≥70, 수익={gain_pct:.1f}%",
|
||||
|
||||
Reference in New Issue
Block a user