자동 종목 매수 시스템 — 매수 기준 분석
backend·monitoring 서비스 전반의 자동 종목선정 및 매수 로직을 코드 기준으로 정리한 문서입니다.
1. 전체 흐름 요약
| 구분 | 담당 | 설명 |
|---|---|---|
| 스케줄 트리거 | monitoring | 설정된 오전 매수 시간(예: 09:05)에 job_morning_buy 실행. 자동 종목선정 ON + 매수 배정 금액 설정 필수. |
| 종목 선정 | backend | stock-selection/run API → StockSelectionRunner.run() → 시간대별 Phase + 2-Pass(Strict/Relaxed) 기준으로 후보 선정. |
| 매수 실행 | monitoring | 선정된 후보 순서대로 시장가 매수. 주문가능현금·예산·95% 안전마진으로 수량 결정. |
재진입: 모니터링 중 탈출 조건 충족 시 자동 매도 후, 자동 종목선정이 켜져 있으면 job_reentry로 종목선정 API 재호출 → 제외 종목 빼고 매수 시도.
2. Backend — 종목 선정 기준
2.1 후보 수집 (Selector)
- 파일:
backend/strategies/selector.py—StockSelector.get_candidates() - 시장: 통합 시장
J(KOSPI + KOSDAQ) - 수집 방식 (중복 제거, 최대 약 90건):
- 거래대금 상위 30 (
rank_type="2") - 등락률 상위 30 (
rank_type="0") — 기존에 없는 종목만 추가 - 거래량 상위 30 (
rank_type="1") — 기존에 없는 종목만 추가
- 거래대금 상위 30 (
2.2 공통 필터 (Filter A — 설정 무관 항상 적용)
- 블랙리스트: 종목명에 다음 포함 시 제외
스팩, 리츠, ETN, ETF, 우B, 홀딩스, 우선주, KODEX, TIGER, KBSTAR, SOL, ACE, KOSEF, HANARO, ARIRANG, TIMEFOLIO, WOORI, HK, 인버스, 레버리지, 선물 - 우선주 제외: 종목코드 마지막 자리
0이 아니면 제외 - 최소 현재가:
min_price(DBStockSelectionConfig.min_price, 기본 2,000원) 미만 제외
2.3 시간대별 Phase 및 적용 기준
Phase 구간 (backend/utils/timezone.py — get_phase_kst()):
| Phase | KST 시간 | 용도 |
|---|---|---|
| morning_rush | 09:00 ~ 09:20 | 장 초반 갭·모멘텀 중심 |
| morning_trend | 09:20 ~ 13:00 | 2-Pass (Strict → Relaxed) |
| afternoon | 13:00 ~ 15:20 | 오후 거래대금·고가대비 강화 |
기준값은 backend/strategies/strategy_runner.py 기본값이며, 사용자별 StockSelectionConfig(DB)가 있으면 해당 값으로 대체됩니다.
Phase 1: Morning Rush (09:00~09:20)
- 기준:
OPENING_CRITERIA(DB:opening_*컬럼) - 항목:
- 최소 거래대금: 기본 10억 원 (
opening_min_amount) - 등락률:
opening_min_rate~opening_max_rate(기본 3% ~ 20%) - 위꼬리:
opening_max_shadow(기본 5%) 이내 - 체결강도:
opening_cttr_min(기본 100) - 시가 갭:
opening_gap_min이상opening_gap_max이하 (기본 2% ~ 9%, 갭락 완화) - VWAP: 이 Phase에서는 사용 안 함 (
vwap_support=False)
- 최소 거래대금: 기본 10억 원 (
Phase 2: Morning Trend (09:20~13:00) — 2-Pass
- Pass 1 — Strict
- 최소 거래대금: 기본 25억 (
strict_min_amount) - 등락률:
strict_min_rate~strict_max_rate(기본 3% ~ 20%) - 위꼬리:
strict_max_shadow(기본 3%) - 체결강도:
strict_cttr_min(기본 110)
- 최소 거래대금: 기본 25억 (
- Pass 2 — Relaxed (Strict 통과 0건일 때만)
- 최소 거래대금: 기본 12억 (
relaxed_min_amount) - 등락률: 기본 1.5% ~ 20% (
relaxed_min_rate,relaxed_max_rate) - 위꼬리: 기본 5% (
relaxed_max_shadow) - 체결강도: 기본 100 (
relaxed_cttr_min) - 고점 대비: 현재가 ≥ 고가 ×
relaxed_high_position_ratio(기본 0.92)
- 최소 거래대금: 기본 12억 (
Phase 3: Afternoon (13:00~15:20)
- 기준:
AFTERNOON_CRITERIA(DB:afternoon_*) - 최소 거래대금: 기본 500억 (
afternoon_min_amount) - 등락률/위꼬리/체결강도:
afternoon_min_rate,afternoon_max_rate,afternoon_max_shadow,afternoon_cttr_min - 고점 대비:
afternoon_high_position_ratio(기본 0.93)
Crisis Mode (KOSDAQ 지수 전일대비 ≤ -1.2%)
- 파일:
backend/services/stock_selection_runner.py—get_kosdaq_change_pct(),CRISIS_THRESHOLD_PCT = -1.2 - 활성화 시 모든 Phase에
CRISIS_CRITERIA적용:- 최소 거래대금 30억, 최소 등락률 0.5%,
vwap_support=True등
- 최소 거래대금 30억, 최소 등락률 0.5%,
2.4 Filter B·C (selector.analyze_stock)
- 양봉: 현재가 ≥ 시가 (음봉 제외)
- 위꼬리: (고가 - 현재가) / 고가 × 100 ≤
max_shadow(설거지 패턴 완화) - 갭: Phase에
gap_min/gap_max있으면 (시가 - 전일종가)/전일종가×100 구간 적용 - VWAP:
vwap_support=True일 때만, 현재가 ≥ 당일 평균 거래단가 - 체결강도(cttr): API에
cttr있으면cttr_min이상만 통과 - 고점 대비:
high_position_ratio있으면 현재가 ≥ 고가 × 비율
2.5 정렬 (선정 순서)
- 정렬 키: 등락률 1% 버킷 내림차순 → 동일 버킷 내 거래대금 내림차순
(selector.sort_candidates()— “Money Moves” 전략)
2.6 예산(budget) 반영
- 파일:
backend/services/stock_selection_runner.py—run() stock-selection/run호출 시 budget(원) 전달 시:- 현재가 ≤ budget 인 종목을 우선 배치하고, 초과 종목은 리스트 뒤로 이동.
- Best = 정렬 후 1순위(예산 만족 가능한 쪽 우선).
2.7 재진입 필터 (당일 매도 이력)
- 파일:
backend/services/stock_selection_runner.py—_apply_reentry_filter() - 입력:
get_today_closed_trades_map(user_id)(당일 매도 완료 거래) - 규칙:
- 손절/본전 (수익률 ≤ 0): 매도 시각 기준 60분 쿨타임 경과 후에만 재진입 허용
- 익절 (수익률 > 0): 매도 시각 기준 15분 쿨타임 경과 후에만 재진입 허용
3. Monitoring — 오전 매수 실행 (job_morning_buy)
파일: monitoring/jobs.py — job_morning_buy(user_id, environment)
3.1 실행 조건 (모두 만족)
- 장 시간: 15:20 KST 미만 (이후에는 루프 종료)
- 자동매매:
AutoTradingConfig.is_active == True - 보유 포지션 없음: 동일 환경
exit_points0건 (이미 보유 시 오전 종목선정 루프 종료) - 매수 배정 금액:
User.default_allocation> 0 (0이면 즉시 중단)
3.2 종목 선정 호출
- API:
POST {API_BASE_URL}/stock-selection/run - Body:
{ "select_best_only": false, "budget": allocated_amount } - Header:
X-User-Id: {user_id}(종목선정 설정·재진입 필터에 사용) - 선정 0건이면 최대 3회 재시도(2초 간격); 3회 연속 0건이면
standby_wait_minutes(기본 20분) 대기 후 while 상단에서 재시도(15:20·설정·포지션 재확인 후 종목선정 다시 실행).
3.3 매수 금액·수량
- 예산:
allocated_amount = User.default_allocation - 종목별:
current_allocated = min(remaining_budget, 주문가능현금)
→ 안전 마진 95%:safe_amount = current_allocated * 0.95
→ 수량:quantity = floor(safe_amount / 현재가) - 수량 0이면 해당 종목 스킵(다음 후보로).
- 주문: 시장가 매수
order_stock_cash(..., order_div="01"). - 한 종목이라도 매수 성공 시 해당 금액만큼
remaining_budget차감 후 다음 후보 계속; 잔여 예산 0이면 종료.
3.4 매수 후 처리
ExitPointManager.set_exit_point()로 탈출지점 등록history_persistence.add_trade_on_buy()로 거래 이력 저장POST .../tabs로 탭 추가
4. Monitoring — 재진입 매수 (job_reentry)
파일: monitoring/jobs.py — job_reentry(excluded_stock_code, user_id, environment)
- 트리거: 모니터링 루프에서 탈출 조건 충족 시 자동 매도 후,
AutoTradingConfig.auto_stock_selection_enabled == True일 때만 재진입 스레드 시작. - 제외: 방금 매도한 종목
excluded_stock_code는 선정 후보에서 제거 후 매수 시도. - 예산: 오전 매수와 동일하게
User.default_allocation. - 정산 대기: 매도 후 5초 대기 후 주문가능현금 재조회.
- 종목선정: 동일
stock-selection/run(budget 전달,X-User-Id전달) → 재진입 필터 적용된 후보 사용. - 매수: 안전 마진 95% → 실패 시 90% → 85% 순으로 재시도(최대 3회). 1건이라도 매수 성공 시 재진입 job 종료; 전부 실패 시 5분 후 종목선정부터 재시도.
- 종료 조건: 장마감 일괄 매도 시각 도달 또는
is_active/auto_stock_selection_enabled비활성 또는 보유 포지션 생김.
5. Monitoring — 스케줄·트리거
파일: monitoring/monitor_loop.py
- 스케줄 스레드: 5초마다
_check_auto_trading_schedule()실행. - 오전 매수:
current_time_str == morning_buy_time(HH:MM, KST) 이고auto_stock_selection_enabled == True일 때,
당일·동일 시간대 중복 트리거 방지 후job_morning_buy(user_id, environment)스레드 시작. - 오후 매도:
afternoon_sell_time에job_afternoon_sell스레드 시작. - 재진입: 위 4절 참고(모니터링 루프 내 매도 성공 시 조건 만족하면
job_reentry스레드 시작).
6. DB·설정 요약
6.1 종목선정 설정 (StockSelectionConfig)
- 스키마:
irm.stock_selection_config, (user_id, environment) 1건. - 주요 컬럼:
min_price,strict_min_amount,strict_min_rate,strict_max_rate,strict_max_shadow,strict_cttr_min,relaxed_min_amount,relaxed_min_rate,relaxed_max_rate,relaxed_max_shadow,relaxed_cttr_min,relaxed_high_position_ratio,opening_min_amount,opening_min_rate,opening_max_rate,opening_gap_min,opening_gap_max,opening_max_shadow,opening_cttr_min,afternoon_min_amount,afternoon_min_rate,afternoon_max_rate,afternoon_max_shadow,afternoon_cttr_min,afternoon_high_position_ratio.
6.2 자동매매 설정 (AutoTradingConfig)
- 오전 매수 시간:
morning_buy_time(예: 09:05:00) - 오후 매도 시간:
afternoon_sell_time - 자동 종목선정:
auto_stock_selection_enabled(False면 오전 매수·재진입 시 종목선정 스킵) - 종목선정 3회 0건 시 대기:
standby_wait_minutes(기본 20)
6.3 사용자 (User)
- 매수 배정 금액:
default_allocation(원). 0이면 오전 매수·재진입 시 매수 실행 안 함.
7. 참고 파일 목록
| 역할 | 경로 |
|---|---|
| Phase·기준값 | backend/strategies/strategy_runner.py |
| 후보 수집·필터·정렬 | backend/strategies/selector.py |
| 종목선정 실행·예산·재진입필터·Crisis | backend/services/stock_selection_runner.py |
| 시간대 Phase | backend/utils/timezone.py |
| 종목선정/자동매매 설정 모델 | backend/model/database.py (StockSelectionConfig, AutoTradingConfig, User) |
| 오전 매수·재진입·오후 매도 job | monitoring/jobs.py |
| 스케줄·모니터링 루프 | monitoring/monitor_loop.py |
| 당일 매도 이력(재진입 필터) | backend/services/history_persistence.py (get_today_closed_trades_map) |
이 문서는 코드 분석 기준으로 작성되었으며, 배포 버전과 다를 수 있습니다.