수정본
This commit is contained in:
@@ -30,7 +30,7 @@ from app.core.database import AsyncSessionLocal
|
||||
from app.core.market_schedule import is_market_open
|
||||
from app.services.mdd_manager import mdd_manager
|
||||
from app.services.swing.exit_service import check_and_execute_exit_v2
|
||||
from app.services.swing.entry_service import execute_swing_entries
|
||||
from app.services.swing.entry_service import execute_swing_entries, scan_dual_strategy_signals
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
_KST = pytz.timezone("Asia/Seoul")
|
||||
@@ -57,6 +57,25 @@ def _normalize_recommendations(rec: dict) -> dict:
|
||||
return rec
|
||||
|
||||
|
||||
def _filter_unsuitable_stocks(rec: dict, unsuitable_codes: set) -> None:
|
||||
"""장 전 AI 분석에서 부적합 판정된 종목을 추천 목록에서 제거."""
|
||||
groups = rec.get("recommendations_by_rule", [])
|
||||
if isinstance(groups, list):
|
||||
for group in groups:
|
||||
if isinstance(group, dict) and "stocks" in group:
|
||||
group["stocks"] = [
|
||||
s for s in group["stocks"]
|
||||
if isinstance(s, dict) and s.get("stock_code") not in unsuitable_codes
|
||||
]
|
||||
elif isinstance(groups, dict):
|
||||
for key, stocks in groups.items():
|
||||
if isinstance(stocks, list):
|
||||
groups[key] = [
|
||||
s for s in stocks
|
||||
if isinstance(s, dict) and s.get("stock_code") not in unsuitable_codes
|
||||
]
|
||||
|
||||
|
||||
async def swing_scheduler() -> None:
|
||||
"""60초 주기 스윙 트레이딩 스케줄러.
|
||||
|
||||
@@ -149,7 +168,25 @@ async def swing_scheduler() -> None:
|
||||
await asyncio.sleep(60)
|
||||
continue
|
||||
|
||||
# 5. MCP RISK_ASSESSMENT
|
||||
# 5. 장 전 AI 분석 결과 활용 + MCP 리스크 평가 (최적화)
|
||||
# 장 전 Sonnet 배치 분석 결과가 Redis에 있으면 부적합 종목을 사전 필터링
|
||||
# MCP RISK_ASSESSMENT는 실제 매수 직전에만 호출 (비용 절감)
|
||||
_ai_filtered_codes = set()
|
||||
try:
|
||||
_ai_pre = await get_json("ai:pre_market:latest")
|
||||
if _ai_pre and isinstance(_ai_pre, dict):
|
||||
for s in _ai_pre.get("suitable_stocks", []):
|
||||
if isinstance(s, dict) and not s.get("suitable", True):
|
||||
_ai_filtered_codes.add(s.get("stock_code", ""))
|
||||
if _ai_filtered_codes:
|
||||
logger.info(f"[Swing] AI 장 전 분석 필터: {len(_ai_filtered_codes)}종목 제외")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 추천 목록에서 AI 부적합 종목 제거
|
||||
if _ai_filtered_codes:
|
||||
_filter_unsuitable_stocks(rec, _ai_filtered_codes)
|
||||
|
||||
recommendations = rec.get("recommendations_by_rule", [])
|
||||
if isinstance(recommendations, dict):
|
||||
rec_count = sum(len(v) for v in recommendations.values())
|
||||
@@ -169,6 +206,12 @@ async def swing_scheduler() -> None:
|
||||
for s in g.get("stocks", [])[:3]
|
||||
][:10]
|
||||
|
||||
if not top_stocks:
|
||||
logger.info("[Swing] 필터링 후 추천 종목 없음 — 스킵")
|
||||
await asyncio.sleep(60)
|
||||
continue
|
||||
|
||||
# MCP 리스크 평가 (Haiku — 실제 진입 직전 1회)
|
||||
trade_plan = {
|
||||
"action": "BUY",
|
||||
"strategy": "swing",
|
||||
@@ -196,7 +239,17 @@ async def swing_scheduler() -> None:
|
||||
async with AsyncSessionLocal() as entry_session:
|
||||
logger.info(f"[Swing] 진입 실행 시작: {top_stocks[:5]}")
|
||||
await execute_swing_entries(rec, entry_session)
|
||||
logger.info("[Swing] ── 사이클 완료 ──")
|
||||
|
||||
# 7. 듀얼 전략 시그널 스캔 (모멘텀 돌파 + 눌림목 매수, CLAUDE.md 8.4)
|
||||
try:
|
||||
async with AsyncSessionLocal() as dual_session:
|
||||
dual_results = await scan_dual_strategy_signals(dual_session)
|
||||
if dual_results:
|
||||
logger.info(f"[Swing] 듀얼 전략 진입: {len(dual_results)}건")
|
||||
except Exception as e:
|
||||
logger.debug(f"[Swing] 듀얼 전략 스캔 오류: {e}")
|
||||
|
||||
logger.info("[Swing] ── 사이클 완료 ──")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[Swing] 스케줄러 사이클 오류: {e}", exc_info=True)
|
||||
|
||||
Reference in New Issue
Block a user