대시보드 수정및 전략수정중

This commit is contained in:
2026-02-27 11:22:44 +09:00
parent 38ff312321
commit 4de03bad67
9 changed files with 159 additions and 65 deletions

View File

@@ -244,14 +244,20 @@ async def execute_auto_trade(stock: dict, db: AsyncSession):
score_val = 0.0
buy_keywords = [
"정배열 눌림목 매수", "눌림목 진입 매수타점", "RSI 바닥 탈출 반등",
"정배열 눌림목 매수", "눌림목 진입 매수타점", "RSI 바닥 탈출 반등",
"RSI 안정 상승", "스토캐스틱 과매도", "5일선 > 20일선", "20일선 > 60일선",
"골든크로스", "볼린저 하한선 터치", "횡보장 — 평균회귀 유리", "거래량 증가와 상승"
]
should_buy = any(k in text_signals for k in buy_keywords)
# 거래량 부족 신호만 있는 경우 매수 금지 — 유효 신호에서 거래량부족 제거 후 판단
signals_without_volume_weak = [s for s in signals if "거래량부족" not in s]
text_signals_filtered = " ".join(signals_without_volume_weak)
should_buy = any(k in text_signals_filtered for k in buy_keywords)
# 거래량부족 신호만 있고 다른 유효 신호가 없으면 매수 금지
if not signals_without_volume_weak and signals:
should_buy = False
sell_keywords = [
"역배열 하락 매도타점", "RSI 과매수", "스토캐스틱 과매수", "데드크로스",
"역배열 하락 매도타점", "RSI 과매수", "스토캐스틱 과매수", "데드크로스",
"이동평균 역배열", "볼린저 상한선 돌파", "손절라인 이탈"
]
should_sell = any(k in text_signals for k in sell_keywords)
@@ -278,11 +284,15 @@ async def execute_auto_trade(stock: dict, db: AsyncSession):
ml_hold_active = settings.ML_ENSEMBLE_ENABLED and ml_signal_trade == "HOLD" and not should_buy
if not ml_hold_active:
should_buy = True
elif not has_rule:
# rule 없는 경우 급등과열 신호도 차단
if "급등과열" in text_signals or "이격도 과열" in text_signals or "매수금지" in text_signals:
return {"action": "HOLD", "stock": stock_name, "reason": "overheat_no_rule"}
if should_buy and not has_rule:
strong_buy_kw = ["RSI 바닥 탈출 반등", "볼린저 하한선 터치", "눌림목 진입 매수타점", "정배열 눌림목 매수"]
has_strong_signal = any(k in text_signals for k in strong_buy_kw)
matched_count = sum(1 for k in buy_keywords if k in text_signals)
has_strong_signal = any(k in text_signals_filtered for k in strong_buy_kw)
matched_count = sum(1 for k in buy_keywords if k in text_signals_filtered)
if not has_strong_signal and (matched_count < 2 or score_val < 2):
should_buy = False
@@ -337,6 +347,20 @@ async def execute_auto_trade(stock: dict, db: AsyncSession):
available_cash, total_assets = 0.0, 0.0
daily_cutoff = _get_kst_now().replace(hour=0, minute=0, second=0, microsecond=0)
# PENDING 포함 오늘 전체 중복 주문 차단 (가장 중요한 체크 — 맨 먼저 실행)
pending_check = await db.execute(
select(TradeHistory).where(
TradeHistory.stock_code == stock_code,
TradeHistory.order_type == "BUY",
TradeHistory.status.in_(['EXECUTED', 'COMPLETED', 'PENDING']),
TradeHistory.created_at >= daily_cutoff,
)
)
pending_orders = pending_check.scalars().all()
if pending_orders:
return {"action": "SKIP", "stock": stock_name, "reason": "already_ordered_today"}
daily_buy_check = await db.execute(
select(TradeHistory)
.where(
@@ -366,18 +390,6 @@ async def execute_auto_trade(stock: dict, db: AsyncSession):
if recent_buys and (_get_kst_now() - recent_buys[0].created_at).total_seconds() < cooldown_min * 60:
return {"action": "HOLD", "stock": stock_name, "reason": "cooldown"}
recent_buy_cutoff = _get_kst_now() - timedelta(minutes=5)
check_recent = await db.execute(
select(TradeHistory).where(
TradeHistory.stock_code == stock_code,
TradeHistory.order_type == "BUY",
TradeHistory.created_at >= recent_buy_cutoff,
TradeHistory.status.in_(['EXECUTED', 'COMPLETED', 'PENDING'])
)
)
if check_recent.first():
return {"action": "SKIP", "stock": stock_name, "reason": "recent_buy_5min"}
mcp_ok, mcp_reason = await _mcp_validate_buy(stock)
if not mcp_ok:
return {"action": "HOLD", "stock": stock_name, "reason": mcp_reason}
@@ -405,17 +417,6 @@ async def execute_auto_trade(stock: dict, db: AsyncSession):
else:
order_result = await korea_investment_api.place_overseas_order(stock_code, "BUY", quantity, float(current_price), exchange_code)
recent_cutoff = _get_kst_now() - timedelta(seconds=10)
duplicate_check = await db.execute(
select(TradeHistory).where(
TradeHistory.stock_code == stock_code,
TradeHistory.order_type == "BUY",
TradeHistory.created_at >= recent_cutoff
)
)
if duplicate_check.scalar_one_or_none():
return {"action": "SKIP", "reason": "duplicate_order", "stock": stock_name}
total_amount = Decimal(str(current_price)) * quantity
entry_reason = f"매수 신호: {', '.join(signals[:3])}. 점수: {score_val}점"
@@ -458,13 +459,10 @@ async def execute_auto_trade(stock: dict, db: AsyncSession):
)
db.add(new_holding)
await db.commit()
try:
from app.api.endpoints.portfolio import portfolio_cache
if portfolio_cache.summary:
old_cash = float(portfolio_cache.summary.get("available_cash", 0))
portfolio_cache.summary["available_cash"] = max(0.0, old_cash - quantity * current_price)
portfolio_cache.change_flag = True
portfolio_cache.apply_buy(stock_code, stock_name, quantity, current_price)
except Exception:
pass
return {"action": "BUY", "stock": stock_name, "quantity": quantity, "price": current_price, "success": success}
@@ -528,9 +526,9 @@ async def execute_auto_trade(stock: dict, db: AsyncSession):
await db.commit()
try:
from app.api.endpoints.portfolio import portfolio_cache
portfolio_cache.holdings = []
portfolio_cache.summary = None
portfolio_cache.change_flag = True
avg = float(holding.average_price or current_price)
portfolio_cache.apply_sell(stock_code, quantity, current_price, avg)
portfolio_cache.holdings = [h for h in portfolio_cache.holdings if h.stock_code != stock_code]
except Exception:
pass
return {"action": "SELL", "stock": stock_name, "quantity": quantity, "price": current_price, "profit_loss": profit_loss_rate, "success": success}