47 KiB
자동 주식 매매 시스템 — Claude 가이드
모든 코드 수정·분석은 이 문서의 규칙을 엄격히 준수할 것. 이 문서는 코드가 실제로 하는 일을 기술한다. 이상(理想)이 아니라 현재 동작이 기준이다. 코드와 이 문서가 어긋나면 코드가 진실이고, 문서를 고치는 것이 1차 대응이다.
0. 파라미터 우선순위 — 가장 먼저 알아야 할 것
전략 파라미터의 진실 소스는 세 곳이고, 다음 순서로 앞선 것이 이깁니다.
adaptive_strategy(_MODE_PARAMS) > strategy_cfg(DB: strategy_configs) > settings(config.py)
adaptive_strategy는 2분 주기로 모드를 갱신하고, RSI 진입·포지션 크기·손절을 덮어씁니다. 모드가fallback_used=True로 떨어질 때만 아래 단계가 쓰입니다.strategy_cfg.get(KEY)는 DB 테이블strategy_configs를 읽습니다. 이 테이블은strategy_config_service.DEFAULT_PARAMS로 시드되지만,auto_init()이on_conflict_do_nothing이라 기존 행은 갱신되지 않습니다.reset_to_default()도 DB의default_value컬럼을 쓰므로 코드만 고쳐서는 반영되지 않습니다.config.py(settings)는 맨 마지막 폴백입니다. §8.2 표가 문서화하는 값이 바로 이것입니다.
따라서 "파라미터가 N이다"라고 주장하기 전에 세 소스를 모두 확인할 것. 2026-09-27 감사에서 이 세 소스가 11곳에서 어긋나 있었고(예: BULL 진입 RSI가 문서 46 / DB 30, 손절이 문서 -7% / DB -6%), 이것이 "문서가 현실과 맞지 않는다"의 근본 원인이었습니다. 2026-09-27 정합 완료: DEFAULT_PARAMS 11건을 config.py 에 맞췄고(AST 전수 대조 후 실질 불일치 0건, 잔여 2건은 부호 규약 차이), alembic 0012_refresh_strategy_config_defaults 가 기존 DB 행을 갱신합니다 — 운영자가 UI 로 직접 바꾼 값은 보존하고 옛 기본값과 같은 행만 갱신합니다.
그래도 우선순위 체계 자체는 그대로이므로, 값을 바꿀 때는 세 곳을 함께 보십시오.
1. 스택 & 환경
- 백엔드: Python 3.11 / FastAPI / SQLAlchemy 2.0(Async) / PostgreSQL 15 / Redis 7 / RabbitMQ 3
- 프론트: React 18 / TypeScript / Vite / Recharts / Lightweight Charts
- 인프라·ML: Docker Compose, Nginx, Alembic / PyTorch, LightGBM, scikit-learn
- 연동: 한국투자증권(KIS) REST·WebSocket, MCP 서버(stdio) / AI 계층:
AI_PROVIDER=claude|gemini - 주요 .env:
KIS_MODE(virtual|real),TOKEN_SAVE_MODE,AUTO_TRADE_ENABLED,DAY_TRADE_ENABLED,US_TRADING_ENABLED(기본 False),MULTI_TENANT_ENABLED(기본 False)
배포 순서 (엄수)
docker compose buildalembic upgrade head를 앱 기동보다 먼저 실행 (docker compose run --rm backend alembic upgrade head)- 스키마 변경 시 필수:
create_all은 기존 테이블에 컬럼을 추가하지 않는다. - 전략 파라미터 마이그레이션(0012 계열)도 마찬가지다. 앱이 먼저 뜨면
strategy_cfg캐시(30초)가 옛 값으로 채워져 최대 30초간 낡은 사이징·손절로 매매가 돈다. 장중 배포라면 특히 위험하다.
- 스키마 변경 시 필수:
docker compose up -d --build—--build필수.up -d만 하면 옛 이미지로 컨테이너가 재생성된다.- 섹터 캐시 생성(최초 1회):
docker compose exec backend python collect_sector_map.py- 없으면
sector_mapper가 44종목 큐레이션 표로만 폴백해 §5.1 섹터 필터가 사실상 무력화된다. - 캐시는
~/.cache/auto-stock/sector_map.json에 생성되고docker-compose.yml의./token_cache:/root/.cache/auto-stock마운트로 유지된다(추가 마운트 불필요). - 상장·폐지 반영을 위해 주기적 재실행 권장(평일 08:30 cron).
- 없으면
2. 구조 (backend/app/)
api/endpoints/— portfolio, market_data, trades, day_trading, backtest 등core/— config(설정), database, security(require_api_key), json_helpers(_dumps), market_schedulemodels/models.py— Portfolio, TradeHistory, ActiveTrade, StockAnalysis, StockBan, StrategyConfig 등realtime/— websocket_client, event_handlerservices/전략 (활성):mean_reversion_strategy(§6, 스윙 3전략 로직 전부 여기),day_trading_strategy(§7),adaptive_strategy(파라미터 오버레이),us_stock_strategy(US_TRADING_ENABLED=False로 정지)services/daytrading/·services/swing/— entry_service, exit_service, position_manager, schedulerservices/순수 계산 모듈 (settings·DB·네트워크 참조 없음 — 이 원칙을 깨지 말 것):indicators_core.py— RSI/BB/ATR/ADX 단일 출처.rsi_wilder/rsi_sma/atr_sma/bollinger_sma/adx_wilder/true_rangessizing_core.py— 포지션 사이징 원시 연산.budget_by_ratios/clamp_by_ratios/apply_confidence_scale/amount_to_quantity/half_kelly_fraction/effective_min_buy_amount. 한도 숫자는 이 모듈에 없다(호출부의 settings 소유)
services/sector_mapper.py— 섹터 조회 단일화. 조회 경로는 파일 캐시만 읽고 네트워크를 쓰지 않는다(§3.1). 수집은backend/collect_sector_map.py배치 전용services/핵심: trade_executor(매매엔진), db_worker(비동기 DB워커), mdd_manager·loss_tracker·correlation_filter(리스크), korea_investment_api·kis_realtime_stream·naver_finance(데이터), feature_engineering·ml_ensemble·technical_indicators(분석), analysis_db_service(DB 기반 스캐너), market_analyzer(국면 게이트), market_playbook(이벤트 차단), ai_strategy_service(AI 필터 계층)mcp-server/— stdio_server, auto_trade_claude, claude_client, gemini_client, trade_monitor, risk_validator, mcp_tools- 배치(
backend/) — batch_daily_analysis, batch_intraday_analysis, train_ensemble, collect_krx_data
2026-09-27 삭제됨 (import 0건 확인 후 제거, 총 2,695줄):
services/short_term_strategy.py,services/kosdaq_scanner.py,app/strategy/패키지 전체 — 기능은 각각day_trading_strategy,analysis_db_service.run_db_scanner(),services/쪽에 이미 있다.app/trading/(kis_client·order_manager·market_data) +services/korea_investment_api_adapter.py— 유일한 진입점인 adapter 가 import 0건이었다.real_market_api의 정본은services/korea_investment_api.py:1482이고adaptive_strategy·market_analyzer가 거기서 import 한다. adapter 가 정의한 동명 객체는 아무도 쓰지 않았다.
되살리지 말 것. 되살리려면 먼저 "누가 import 하는가"를 grep 으로 확인하라.
config.py 의 SHORT_TERM_* 키들은 삭제된 전략의 잔재로 현재 아무 효과가 없습니다(백테스트 폴백에서만 참조).
3. 아키텍처 원칙 (필수)
- 실시간 계층 DB I/O 절대 금지 — API 콜백·웹소켓·실시간 스케줄러에서
AsyncSessionLocal()/commit()호출 금지. - 모든 DB 쓰기는
db_worker.py의db_task_queue경유 (실패 시 지수 백오프 1→2→4초, 최대 3회 재시도).- 허용 예외 ①:
sync_portfolio()— 단일 트랜잭션 필요. - 허용 예외 ②: auth/credentials/bootstrap 시드(회원가입·로그인,
user_kis_credentials등록/삭제, 기동 시 관리자·자격증명 시드) — 실시간 계층이 아님. - 허용 예외 ③ (읽기 전용):
daytrading/exit_service.close_stale_positions,daytrading/entry_service.populate_daytrading_watchlist,daytrading/scheduler기동 시restore_from_db— SELECT 전용이며commit()없음. 각 지점에 주석으로 명시되어 있다. 여기에 쓰기를 추가하지 말 것.
- 허용 예외 ①:
- 지원 task: insert_trade_history, update_trade_history(+
_by_order_id), upsert_stock_analysis, bulk_upsert_stock_analysis, upsert_stock_ban, upsert_portfolio, upsert_active_trade, close_active_trade, insert_trade_journal, log_mcp_deny- 전략명 필터 (2026-09-27 추가):
close_active_trade/upsert_active_tradepayload 가 세 가지 선택 키를 받는다. 우선순위는strategy_name(동등) >strategy_names(IN) >strategy_name_not(NOT IN,strategy_name IS NULL행은 제외되지 않음). 키를 넘기지 않으면 동작은 이전과 동일하다. db_worker.not_dt_strategy_clause(column)가 "DT 아닌 행"의 정본 표현이다(DT_STRATEGY_NAMES = ("day_trade","daytrading")).trade_executor·swing/position_manager·swing/exit_service·mdd_manager가 모두 이것을 쓴다 — 한 곳만 바꾸면 더 나쁜 불일치가 된다.
- 전략명 필터 (2026-09-27 추가):
- 동시성: trade_executor
_order_semaphores(종목별 동시 주문 방지) / Redis 분산 락(워커 간 동시 매도 방지) / loss_tracker·mdd_managerasyncio.Lock. 동시 접근 경로에서는is_banned_safe(),record_trade_result_safe()사용. Portfolio행은stock_codeUNIQUE 라 같은 종목의 스윙+데이트레이딩 수량이 합산되어 있다. 한 전략이 청산할 때는 반드시 다른 전략 보유분을 차감할 것 (trade_executor._execute_sell,swing/exit_service양쪽에 구현됨). 차감을 빼먹으면 과다매도·주문거부가 난다.
4. 코딩 규칙
- 기존 함수/클래스 시그니처 변경 금지 → 새 전략은
_v2/_v3, 동기→비동기 확장은_safe, 내부 구현 분기는_impl접미사의 새 메서드. - JSON은
json.dumps금지,core.json_helpers._dumps()사용. - 쓰기/삭제 엔드포인트에
Depends(require_api_key)필수 (portfolio·trades·day_trading 등은app/api/__init__.py에서 라우터 단위로 적용됨). 외부 API 호출verify=True유지(KIS 포함).SECRET_KEY64자+ 랜덤. - 프롬프트는 토큰 최소화, 컴팩트 포맷.
- 리스크 가드·한도 클램프를
try블록 안에 두지 말 것. 예외가 나면 가드가 조용히 통과된다 (2026-09-27에 4% 상한이 정확히 이 이유로 무력화되어 있었다). 예외 로그는logger.debug금지 — 최소warning. - 로그·description 문자열에 임계값을 하드코딩하지 말 것. 실효값을 f-string 으로 넣어라. 2026-09-27 에
mean_reversion_strategy의 BULL 진입 description 이RSI<40을 박아둔 채 실제로는 46 을 쓰고 있었다 — 로그·TradeJournal·ML 학습 데이터에 거짓 임계값이 남는다.
4.1 테스트 규약 (안전 직결)
2026-09-27 에 유닛 테스트가 실제 KIS 에 매도 주문을 2회 전송하고 있었다. 리팩터링이 close_stale_positions() 의 호출 대상을 _execute_exit → _execute_exit_stale 로 바꿨는데 테스트 목이 전자에 걸려 있어 빗나갔고, 실주문 경로가 그대로 실행됐다. 별도로 test_api_endpoints.py 도 캡처된 싱글톤 참조 누수로 실제 잔고 조회를 타 141초를 소비했다.
- 실행:
cd backend && DATABASE_URL="...autostock_test" ../.venv/bin/python -m pytest tests/ --ignore=tests/integrationbackend/tests/conftest.py가DATABASE_URL의 DB 이름이_test로 끝나지 않으면 세션을 중단한다.- 같은 conftest 가 주문·토큰·시세·잔고 계열을 호출 즉시 예외로 치환한다. 차단 예외는
BaseException파생(KISCallBlocked) — 프로덕션의except Exception:폴백에 삼켜지면 차단이 무력화되기 때문이다. - 차단은 클래스 메서드(
KoreaInvestmentAPI.<method>)에 적용한다. 인스턴스만 패치하면from ... import korea_investment_api로 참조를 캡처한 모듈에 새어나간다. - 옵트아웃은 세션 단위
--allow-network하나뿐이다. per-test 예외 마커를 만들지 말 것.
- 목킹 지점은 공통 종단에 걸어라. 위임 래퍼(
_execute_exit/_execute_exit_stale)가 아니라_execute_exit_impl처럼 양쪽이 수렴하는 곳에 걸어야 리팩터링에 빗나가지 않는다. - 차단·목킹은 예외 발생형으로. 그럴듯한 값을 반환하는 스텁은 목킹 누락을 숨겨 거짓 통과를 만든다.
- 테스트가 수십 초 걸리면 실호출 신호다.
korea_investment_api._handle_rate_limit()의 "토큰 발급 1분 제한"asyncio.sleep(≈70초)에 걸린 것이다. 소켓을 막아도 예외가 폴백에 삼켜진 뒤 잠들기 때문에 느린 것은 그대로다. - 실호출 없음의 유일한 증거는 소켓 차단 하 통과다.
socket.getaddrinfo/create_connection/socket.connect/connect_ex를 예외로 치환한 pytest 플러그인으로 확인하라(스크래치패드에 두고 프로젝트에 남기지 말 것). sys.modules를 import 시점에 패치하는 방식은 순서 의존을 만든다. 전체 스위트에서는 앞선 파일이 이미 대상 모듈을 로드해 목이 샌다. 모듈 전역 참조는 테스트 시점 autouse 픽스처로 덮어라(test_mean_reversion.py가 이 이유로 단독 74 통과 / 전체 1 실패였다).- 테스트 기대값에 config 값을 하드코딩하지 말 것.
settings.*를 참조하라. 2026-09-27 에MAX_POSITION_SIZE(0.06→0.04)·SWING_MAX_CAPITAL_RATIO(0.50→0.70) 변경이 반영되지 않은 테스트 2건이 이 이유로 깨져 있었다.
5. 리스크 규칙
5.1 사이징·필터
- 종목당 4% (
MAX_POSITION_SIZE) — 절대 상한(hard ceiling). 적응형 모드가 6~7%를 지시해도 소비 지점에서min(_adp.position_size, MAX_POSITION_SIZE)로 클램프되고, 패턴메모리 조정 후에도try밖에서 재클램프된다. - 최대 20종목(
MAX_HOLDINGS_COUNT), 레버리지/인버스 ETF 최대 20%(LEVERAGE_ETF_MAX_RATIO), 전 전략 합산 현금 사용 상한 95%(MAX_TOTAL_CAPITAL_USAGE). - 최소 주문금액 15만원(
MIN_BUY_AMOUNT_KRW) — 단 실효값은min(150,000, 총자산 × 적응형포지션상한 × 0.9)다. 소액계좌에서 주문이 전부 탈락하는 것을 막기 위한 의도적 완화(2026-09-23 하루 141건 차단 사례). - 가격 필터: 국내 >5,000원, 미국 >$4 (
MIN_PRICE_KRW,MIN_PRICE_USD). ETF 예외는trade_executor경로에만 있고market_analyzer·portfolio_v2·manual_buy경로에는 없어 경로별로 동작이 다르다. - 상관관계: 섹터당 최대 3종목, 기존 포트폴리오와 상관계수 >0.7 진입 거부 (
correlation_filter).- 섹터 매핑 (2026-09-27 확충):
sector_mapper가 네이버 업종 API(m.stock.naver.com/api/stocks/industry)로 수집한 캐시를 쓴다. 실측 커버리지 97.27%(747/768) — 미매핑 21종목은 전부 ETF/ETN 이라 실질 주식 커버리지 100%. 조회 순서: 메모리 → 큐레이션 정적 표(44종목, 의도적 그룹핑이라 외부보다 우선) → 파일 캐시 →None.- 업종 79개 → 정본 섹터 20개. 네이버
no=25 "기타"(1,542종목: ETF/ETN/우선주/스팩)는 수집 단계에서 제외해 거대 버킷을 만들지 않는다(최대 버킷 9.6%). - ⚠️
industryAPI 는page/pageSize를 주지 않으면 20개 업종만 반환하고 나머지가 "기타"로 뭉쳐 보인다. 수집 코드를 고칠 때 주의. - 배포 후
collect_sector_map.py를 한 번 돌려야 캐시가 생긴다(§1-4). 없으면 44종목 표로만 폴백.
- 업종 79개 → 정본 섹터 20개. 네이버
- 미매핑 종목의 섹터캡:
SECTOR_CAP_UNMAPPED_ENABLED(기본 False). 미매핑을 "UNKNOWN" 단일 버킷으로 묶어 3종목 한도를 적용하는 스위치다. 켜기 전 조건 — 운영 환경 커버리지 ≥95% & 캐시 7일 이내, 미매핑 잔여가 ETF/ETN 만(일반 주식이 남아 있으면 UNKNOWN 3종목 상한에 걸려 거래가 죽는다), 최대 버킷 <20%, 그리고 켠 상태 백테스트에서 체결 건수가 유의미하게 줄지 않고MAX_HOLDINGS_COUNT=20도달이 가능함을 확인.
- 섹터 매핑 (2026-09-27 확충):
- 연속 손실: 동일 종목 3연패 →
CONSECUTIVE_LOSS_BAN_DAYS(42캘린더일 ≈ 30거래일) 진입 금지 (loss_tracker). 값은 config 연동이며 하드코딩이 아니다.record_trade_result()후 반드시save_to_db()(db_workerupsert_stock_ban경유). 재시작 시load_from_db()복원.
5.2 SL/TP (일봉/스윙)
- 기본 SL: -7%(안정) / -10%(변동성, ATR/종가 > 3%). ATR SL:
진입가 − 2.5×ATR(ATR_STOP_MULTIPLIER). - SL은 기본/ATR 중 타이트한 쪽. 비교 기준에 따라 연산자가 다르다 — 혼동 금지:
- 가격 기준 비교: 더 높은 손절가가 타이트 →
max(atr_stop, pct_stop)이 올바르다. - 음수 퍼센트 기준 비교: 더 큰(0에 가까운) 값이 타이트 →
max()가 올바르다. stop_loss_pct = -abs(...)부호 가드를 항상 유지한다(양수가 들어가면 전 종목 매도 사고).
- 가격 기준 비교: 더 높은 손절가가 타이트 →
- TP: +2R 도달 시 50% 분할 매도, 잔량 트레일링 스탑
고점 − 1.5×ATR. - 분할매도
sell_pct는 "진입 시 원본 수량 대비" 비율이다.remaining_pct게이트(1.0→0.70→0.40)가 원본 누적 전제로 설계되어 있으므로 잔량 기준으로 바꾸면 같은 단계가 재트리거된다.
5.3 MDD (mdd_manager — swing/scheduler.py 에서 매 사이클 자동 호출, 중복 실행 방지 내장)
- -10%: 신규 진입 중단 + 포지션 30% 축소 + 데이트레이딩 중단 (REDUCE_AND_PAUSE)
- -20%: 전 포지션 50% 강제 축소, 레버리지/인버스 ETF 우선 청산 (FORCE_REDUCE)
- 잔고가 peak 갱신 시 모든 MDD 플래그 리셋.
- MDD 갱신은
swing/scheduler.py한 곳이 유일한 실효 경로다(portfolio.py경로는 deprecated). 갱신이 실패하면 peak/drawdown이 정지해 -10%/-20% 게이트가 영구 미발동한다. - fail-safe (2026-09-27 적용): 갱신이
MDD_BLIND_FAILURE_THRESHOLD(5)회 연속 실패하면can_open_new_position()이False를 반환해 모든 신규 매수가 차단된다(MDD_BLIND_BLOCK_ENTRY기본 True). 스윙 진입·수동매수 API·미국장·데이트레이딩 진입·trade_executor전 경로에 일괄 적용되고, 청산 경로는 이 메서드를 호출하지 않아 영향이 없다. 빈 계좌(tot_evlu_amt<=0)는 정상 응답으로 분류되어 차단 사유가 아니다. 갱신 1회 성공이면 재시작 없이 자동 해제.- ⚠️ KIS 장애가 5분 연속되면 당일 매수 0건이 될 수 있다. 토큰 충돌·계좌 교체(40910000)·네트워크 단절이 해당한다.
[MDD] 🚨 신규 진입 차단(fail-safe)error 로그로 즉시 식별되고, 급하면MDD_BLIND_BLOCK_ENTRY=false로 끌 수 있으나 그 상태의 진입은 -20% 구간에서도 무제한 추가 진입을 허용한다는 뜻이다. - 상태 조회:
mdd_manager.get_update_health()/swing.scheduler.get_mdd_update_health()
- ⚠️ KIS 장애가 5분 연속되면 당일 매수 0건이 될 수 있다. 토큰 충돌·계좌 교체(40910000)·네트워크 단절이 해당한다.
- MDD 축소가
ActiveTrade를 닫을 때는 스윙 전략명을 명시한다(_sync_active_trade_after_reduction). 전략명 없이 닫으면 db_worker가 "가장 최근 active 행"을 닫아 DT 행을 오폐기한다.
6. 스윙 전략 (mean_reversion_strategy, swing/) — 스윙 자본 최대 70%(SWING_MAX_CAPITAL_RATIO)
세 전략이 한 파일에 있고 모두 swing/entry_service 에서 호출됩니다.
| 전략 | 진입 | 청산 | 국면 조건 |
|---|---|---|---|
| 평균회귀 | RSI 과매도 + BB하단(%B) | RSI 55 부분 → RSI 70 전량 | BULL·BEAR |
| 모멘텀 돌파 | 20일 신고가 + 거래량 2× + RSI 50~70 | +10% 30% → +20% 30% → 트레일링 | BULL 전용 |
| 눌림목 매수 | MA20>MA60 + MA20 −3% |
평균회귀와 동일 | BULL 전용 |
6.1 시장 국면 (일봉)
- BULL = 코스피200 > MA200 & MA200 기울기 > 0
- BEAR = 코스피200 < MA200 & 기울기 < 0
- SIDEWAYS → 모든 진입 무시 (market_analyzer: position_multiplier=0, max_trades=0)
- 판정은
ADX < 18AND 기울기 ≈ 0 동시 충족 (히스테리시스: 진입 ADX<18 / 해제 ADX>22). 기울기 비교 기간은 20일.
- 판정은
- 국면 커버리지 불균형: 신호원 3개 중 2개가 BULL 전용이고, BEAR는 평균회귀 분기 하나, SIDEWAYS는 담당 전략이 없다(진입 차단이 곧 전략). 코스피가 BULL이 아닌 구간에서는 사실상 거래가 나오지 않는다.
- 2026-09-27: 두 빈 칸을 채우는 코드를 넣었으나 둘 다 플래그 OFF 로 출고했다. DB(NAS) 다운으로 백테스트를 돌릴 수 없고, 게다가 기존 백테스트 자체가 실전과 다른 지표로 돌아간 것이 밝혀져(§8.4) 참고할 근거가 없다.
- 박스권:
services/range_trading_strategy.py+swing/entry_service.scan_range_trading_signals.RANGE_TRADE_ENABLED(기본 False). 스케줄러 배선도 아직 하지 않았다 — 호출부가 없어 현재 실행 경로에 존재하지 않는다. - BEAR 완화:
mean_reversion_strategy.check_bear_buy_signal_v2(점수제).MEAN_REVERSION_BEAR_V2_ENABLED(기본 False). OFF 면 기존 4중 AND 경로가 그대로 동작한다.
- 박스권:
- 측정된 문제 크기: 같은 합성 샘플에서 BEAR 발화율 2.3% vs BULL 13.2% — 실질 게이트 8개가 전부 AND 였다. v2 는 12.4%(5.4배)로 올라가고 구 발화를 모두 포함하는 상위집합이다.
- 2026-09-27: 두 빈 칸을 채우는 코드를 넣었으나 둘 다 플래그 OFF 로 출고했다. DB(NAS) 다운으로 백테스트를 돌릴 수 없고, 게다가 기존 백테스트 자체가 실전과 다른 지표로 돌아간 것이 밝혀져(§8.4) 참고할 근거가 없다.
6.2 평균회귀 진입 조건 (실제 코드)
- 공통 선행 필터 (BULL·BEAR 모두):
current_volume < avg_volume_20d→ 진입 거부 (거래량 하한, 무조건 선적용)- own_trend 게이트: 종목 MA20 < MA60 × 0.97 → 진입 거부
- RSI 반등 필수:
rsi <= rsi_prev면 진입 거부 (RSI 하락 중 진입 금지)
- BULL: RSI < 46 (
MEAN_REVERSION_RSI_THRESHOLD) + %B 임계값 방식으로 BB 하단 근접 판정- %B 임계: RSI<25 → 0.35 / RSI<30 → 0.30 / 그 외 → 0.20, 여기에 적응형
bb_pct_b/0.30스케일(상한 1.3) 적용 - ※ 과거 문서의 "BB하단 −3%~+1.5% 가격 밴드"는 코드에 존재하지 않는다.
- %B 임계: RSI<25 → 0.35 / RSI<30 → 0.30 / 그 외 → 0.20, 여기에 적응형
- BEAR: RSI < 35 (
MEAN_REVERSION_BEAR_RSI_THRESHOLD) + %B ≤ 0.20 + 거래량 > 1.5×20MA + 반전캔들 - 선제 진입(60분봉 RSI<30 + MA5 상향돌파)은 구현되어 있지 않다. 헬퍼
check_h1_ma5_breakout은 호출부가 없다. 기능이 필요하면 새로 배선할 것.
6.3 평균회귀 청산
- 50% 부분매도: RSI ≥ 55 or 가격 > BB중앙 or 수익 > 2×ATR — 단 최소수익 0.5% 가드가 있다
- 전량: RSI ≥ 70 or 가격 > BB상단(99%) or 트레일링(고점 − 1.5×ATR)
- 시간손절: 20일 보유 + 3% 미달 시 전량 (
check_time_stop_exit)
6.4 문서화되지 않았던 진입 차단 게이트 (전부 실재하며 거래 빈도에 큰 영향)
- ML 앙상블 HOLD 게이트 (
trade_executor): ML이 HOLD이고 신뢰도 ≥60% 면 rule 매수를 차단 - 지수 서킷브레이커 (
swing/entry_service): KODEX200 종가 < MA20 이면 신규 진입 전면 중단 - 마켓 플레이북 (
market_playbook): FOMC·옵션만기 등 이벤트 당일 진입 차단 - 거시/국면 position_multiplier (
market_analyzer): 장외 0.5, CRASH 0.0~0.1, BEAR ratio - 적응형 Crisis 모드: 진입 전면 차단 (§8.4)
- 추격매수 방지·추세 필터·이격도 과열·일일 매매한도·재매수 쿨다운 (
trade_executor) - AI 전략 계층 (
ai_strategy_service): 08:30 장전 분석 / 08:50 DT 후보 선정 / 뉴스 감시 / 15:40 장후 리뷰. 적합성 필터링과 뉴스 악재 배율로 실제 매매에 영향을 준다. - 듀얼 전략 후보 소스 의존:
StockAnalysis.analysis_date >= today에만 의존하므로 일일 배치가 실패하면 국면이 BULL이어도 후보 0건으로 조용히 아무 일도 일어나지 않는다.
2026-09-27 해소된 치명적 결함 2건 (이전에는 듀얼 전략이 사실상 매수를 못 했다):
scan_dual_strategy_signals가"signal"(단수 문자열)만 넣고"signals"(복수 리스트)를 넣지 않아,trade_executor의should_buy = any(k in "" for k in BUY_KEYWORDS)가 항상 False 였다.rule_id도 없어 rule 강제 매수 경로도 타지 않았다 → ML 앙상블이 독립적으로 BUY 를 낼 때만 진입했다. §8.3 의 "3종 병행" 서술과 어긋났다.- 비-rule 요건은
has_strong_signal OR (matched_count >= 2 AND score_val >= 4)인데 듀얼의score는 0.8/0.6 이라 STRONG 신호가 필수다. 모멘텀 돌파를 표현하는 값이 없어BuySignal.신고가돌파("20일 신고가 돌파")를 신설했다. - 신호 문자열은 실제 진입 근거와 일치해야 한다. 통과시키려고 맞지 않는 키워드를 끼우면 로그·저널·ML 학습 데이터가 오염된다(예: 모멘텀에
RSI 안정 상승은 정의가45~60이라 60~70 구간에서 거짓).
- 비-rule 요건은
execute_auto_trade_background가trade_results를 채우고도return문이 없어 항상None을 반환했다 → 호출부의isinstance(result, list)가 영구 False → "진입 n건" 로그가 나오지 않았다. 매매는 정상 수행되고 결과만 유실됐다. 이제 종료 경로 6개 전부 list 를 반환한다.- 반환 리스트에는
action="SELL"과success=False도 섞인다. "진입 n건"은action=="BUY" and success로 세야 한다(호출부 3곳 정정됨).
- 반환 리스트에는
거래가 나오지 않을 때는 전략 로직보다 이 목록을 먼저 확인할 것.
7. 데이트레이딩 (5분봉, day_trading_strategy, daytrading/) — 자본 최대 30%(DAY_TRADE_MAX_RATIO_DT, 스윙:DT = 70:30)
MDD -10% 시 비활성. CRASH/HALTED 국면 차단.
| 전략 | 진입 | SL |
|---|---|---|
| ORB | 시초 레인지 돌파(+0.2% 여유) + 거래량 ≥1.5×평균분당 + 직전 5분봉 양봉 + 갭 4%↑ 거부 + 레인지폭 0.3% 미만 거부 | max(ORB저점, 현재가×(1−3%)) → ATR 하한 → 최소 손절폭 -2% 클램프 |
| VWAP | VWAP 지지 반등(−1%~+0.5%) + RSI(14) ≤ 45 + 거래량 ≥1.3× + 양봉 | ATR 있으면 현재가 − 1.5×ATR, 없으면 −3.5% → 최소 손절폭 -2% 보장 |
| RSI_BB | RSI ≤ 35 + 종가 ≤ BB하단×1.01 + 거래량 ≥1.3×(직전 봉 대비) + 양봉 | VWAP과 동일 |
- 최소 손절폭 보장(
DAY_TRADE_MIN_STOP_LOSS_PCT= 2%)은 §5.2의 "타이트한 쪽" 규칙의 의도적 예외다. 장중 전략에서 손절이 너무 타이트하면 노이즈에 털리므로,min(raw_stop, min_stop)으로 더 느슨한 쪽을 고른다. 이min()을 "버그"로 판단해 뒤집지 말 것. - 사이징: 종목당 6%(
DAY_TRADE_MAX_CAPITAL_RATIO), 동시 5종목(DAY_TRADE_MAX_POSITIONS) = 5×6% = 30%. confidence 조정예산 × min(1.2, confidence/0.7)후 세 한도(종목당 6% / DT 잔여 30% / 가용현금)로 재클램프된다. - 청산: 1.5R(
DAY_TRADE_TARGET_MULTIPLIER) 도달 시 50% 분할 → 잔량 트레일링(ATR×1.5, 발동 최소수익 1%) / 진입 30분(6봉)간 -0.3% 미만이면 시간손절(수익 중이면 보유 유지) / 5분 RSI ≥70 & 수수료 이상 수익 시 / 15:20 EOD 전량 청산 / 전일 미청산 포지션은 장중 발견 즉시 전량 청산(close_stale_positions) - 전일 포지션 청산 손익은 당일 실현손익에 넣지 않는다(
_finalize_exit_impl(count_daily_pnl=False)). 넣으면 당일 매매 0건인데 -3% 손실한도가 발동해 정상 포지션까지 전량 청산된다. 전일 손익은get_stale_realized_pnl()+ warning 로그 + TradeHistory로 추적한다. stale 청산 종목은closed_today에 추가해 당일 재진입을 막는다(주문 실패 시에도). - 분봉 조회 실패 시 폴백 손절이 있다(
_check_fallback_stop_loss). 현재가만으로 손절선 이탈을 판정해 전량 청산한다. 폴백 손절가는min()으로 고른 가장 타이트한 하드캡(-2%) 기준이며, 현재가조차 못 구하면logger.error로 수동 확인을 요청한다. - 제한: 15:00 이후 신규 마감(
DAY_TRADE_ENTRY_CUTOFF_*) / 일일 -3% 손실 시 당일 중단(DAY_TRADE_DAILY_LOSS_LIMIT) / 당일 3연패 쿨다운(position_manager) / 전량 매도 시 loss_tracker 기록 +save_to_db() - 감시종목: 09:00에 당일 분석이 없으면 최근 분석일 결과로 선채움(ORB 수집 보장), 자동 목록은 30분마다 갱신(수동 설정 목록은 유지)
- 1분봉 데이터 주의:
get_minute_chart_full_day는 과거 데이터 포함 요청(FID_PW_DATA_INCU_YN="Y")이라 전일 캔들이 섞인다. dedupe·정렬 키에stck_bsop_date를 포함하고 당일 외 캔들을 제외해야 ORB 거래량(candles_1min[-1])·VWAP이 오염되지 않는다.
8. Claude 지침
8.1 체크포인트
- 파라미터 값을 주장하기 전에 §0의 세 소스를 모두 확인 (adaptive / strategy_cfg(DB) / config.py)
- DB 쓰기 = db_worker 경유 (예외: §3.2의 3종)
- 모든 매매에 §5 리스크 규칙 적용
- SIDEWAYS 국면 진입 제안 금지
- 백테스트·Walk-forward 없이 실전 투입 금지 — 단 §8.5의 백테스트 신뢰성 주의사항을 먼저 읽을 것
- SL 선택 = 타이트한 쪽. 단 비교 기준(가격 vs 음수 퍼센트)에 따라 연산자가 다르고, 장중 최소 손절폭 보장은 의도적 예외 (§5.2, §7)
record_trade_result()후save_to_db()호출 확인- 쓰기/삭제 API에
require_api_key필수 - JSON 출력 시
_dumps()/ 새 기능은 새 파일 / 기존 시그니처 수정 금지 - 리스크 가드·한도 클램프를
try안에 두지 말 것. 예외 로그에logger.debug금지 - 청산은 항상 매수보다 우선. 리스크 가드(MDD/밴/보유수/쿨다운)는 신규 매수만 차단하고 청산 판정은 통과시킬 것
- 한 전략이 청산할 때 다른 전략 보유분 차감 확인 (§3.5)
8.2 config.py 주요값
| 설정 | 값 | 비고 |
|---|---|---|
MAX_POSITION_SIZE |
0.04 | 절대 상한. 적응형보다 우선 |
MIN_BUY_AMOUNT_KRW |
150,000 | 실효값은 소액계좌에서 하향 (§5.1) |
MAX_HOLDINGS_COUNT |
20 | |
MAX_TOTAL_CAPITAL_USAGE |
0.95 | 현금 5% 유보 |
LEVERAGE_ETF_MAX_RATIO |
0.20 | |
ATR_STOP_MULTIPLIER |
2.5 | 스윙 ATR SL 배수 |
STOP_LOSS_PERCENT |
7.0 | config는 양수, DB는 -7.0 (부호 규약 차이) |
STOP_LOSS_HIGH_VOL |
10.0 | config는 양수, DB는 -10.0 |
MEAN_REVERSION_RSI_THRESHOLD |
46.0 | |
MEAN_REVERSION_BEAR_RSI_THRESHOLD |
35.0 | |
MEAN_REVERSION_RSI_EXIT_THRESHOLD |
55.0 | 1차 익절 |
MEAN_REVERSION_RSI_FULL_EXIT |
70.0 | 전량 청산 |
DAY_TRADE_MAX_POSITIONS |
5 | 5×6% = 30% |
DAY_TRADE_MAX_CAPITAL_RATIO |
0.06 | 종목당 |
DAY_TRADE_ORB_STOP_LOSS_PCT |
0.03 | |
DAY_TRADE_REVERSION_STOP_LOSS_PCT |
0.035 | VWAP/RSI_BB |
DAY_TRADE_MIN_STOP_LOSS_PCT |
0.02 | 최소 손절폭 (느슨한 쪽 선택) |
DAY_TRADE_TARGET_MULTIPLIER |
1.5 | 1.5R |
DAY_TRADE_TRAILING_ATR_MULT |
1.5 | |
DAY_TRADE_VWAP_RSI_THRESHOLD |
45.0 | |
DAY_TRADE_RSI_BB_RSI_THRESHOLD |
35.0 | |
DAY_TRADE_TIME_STOP_MIN_GAIN |
-0.003 | -0.3% 미만일 때 청산 |
DAY_TRADE_ENTRY_CUTOFF |
15:00 | |
DAY_TRADE_MAX_RATIO_DT / SWING_MAX_CAPITAL_RATIO |
0.30 / 0.70 | 합 100% |
CONSECUTIVE_LOSS_BAN_COUNT / _DAYS |
3 / 42 | 캘린더일 |
SECTOR_CAP_UNMAPPED_ENABLED |
False | 켜기 전 §5.1 확인 |
MDD_BLIND_BLOCK_ENTRY / _FAILURE_THRESHOLD |
True / 5 | MDD 갱신 실패 시 신규 진입 차단 (§5.3) |
RANGE_TRADE_ENABLED |
False | 박스권 전략. 백테스트 미검증 — True 금지 (§6.1) |
MEAN_REVERSION_BEAR_V2_ENABLED |
False | BEAR 점수제. 백테스트 미검증 — True 금지 (§6.1) |
8.3 적응형 모드 (adaptive_strategy.py, 2분 주기 자동 전환)
| 모드 | RSI 진입 | 포지션 | SL | 트리거 | 해제 |
|---|---|---|---|---|---|
| Aggressive | 50 | 7% → 4%로 클램프 | -9% | ATR백분위<30 AND ADX>25 AND 5일수익>+2% | ATR<40 & ADX>20 & 5일>0 |
| Normal | 46 | 6% → 4%로 클램프 | -7% | 기본 | — |
| Defensive | 38 | 4% | -6% | ATR백분위>70 OR 5일수익<-3% | ATR<60 & 5일>-1% |
| Crisis | 25 | 1.5% | -3% | ATR백분위>90 OR 5일수익<-5% OR MDD < -10% | ATR<80 & 5일>-3% & MDD>-8% |
_MODE_PARAMS숫자는 문서·백테스트 기준값이므로 바꾸지 말 것. 상한 제약은 소비 지점에서 클램프한다.- Crisis 는 파라미터 조정이 아니라 on/off 스위치다:
max_daily_trades=0으로 스윙·DT 신규 진입을 전면 차단한다. MDD -10%가 곧 Crisis를 유발하므로 §5.3의 REDUCE_AND_PAUSE와 이중으로 걸린다. - 모드 해제에는 히스테리시스 밴드가 있어 진입/해제 임계가 다르다(위 표).
8.4 백테스트 신뢰성 — ❌ 기존 결과물 전부 폐기
2026-09-27 조사 결론: 파라미터를 검증했다는 백테스트가 실전 로직을 돌린 적이 없다. 세 가지가 겹쳤다.
① run_backtest.calculate_adx_simple 이 정확히 period 배(14.0×)로 발산했다.
DX(0100)에 합산형 평활을 적용한 버그. 측정: 87%.
Aggressive 전환 빈도 5.6% → 2.0% (−65%). Aggressive 는 포지션 7%·SL −9% 이므로, 기존 백테스트는 해당 구간에서 실전보다 큰 사이즈·느슨한 손절로 돌았다 = 성과 상방 편향.up 1361 vs 97 / flat 165 vs 12 / vol 206 vs 15 — 전 패턴에서 배율 14.0.
결과적으로 §8.3 Aggressive 게이트 ADX > 25 가 모든 레짐에서 100% 통과했다. 수정 후 43
② 백테스트 RSI 는 SMA, 실전은 Wilder 였다.
측정(n≈70,000): mean|Δ| 4.7~6.5, p95 ≈ 15, max ≈ 35. 진입 게이트 RSI<46 판정이 10~11% 의 봉에서 불일치, 전량청산 RSI≥70 은 6~10% 불일치.
③ 미국 백테스트 RSI 가 1봉 지연이었다(같은 엔진의 BB 는 당일 포함, 실전도 당일 포함). 진입 게이트 판정 8.4% 불일치.
세 건 모두 indicators_core 로 정합화했다(룩어헤드 없음을 절단 테스트로 증명 — 각 i 의 값이 closes[:i+1] 만으로 결정됨).
backend/backtest_results/ 의 25개 JSON 은 인용 금지. 근거:
- 전부 2026-09-06 생성 = ADX 발산 수정(2026-09-16
a097381) 이전 코드의 산출물. 당시 SIDEWAYS 게이트 ADX 도 발산 상태여서 §6.1 의 "SIDEWAYS 전면 진입 무시"가 사실상 적용되지 않았다. total_trades1013건 — 파라미터당 최소 100거래 기준 대비 2자리수 부족. Sharpe −2.2−2.6 자체가 통계적 무의미.params.adaptive_modes.normal.rsi_entry가 파일별로 20/43/70 으로 흔들림 = optimizer 그리드 스윕 산출물. IS/OOS 분리·walk-forward 기록 없음.- 저장 기간은 2024-09 이후뿐이고 파라미터도 현행 config(4%/7.0/46)와 불일치(0.05/−6.0/43) → §8.2 표 주석의 "백테스트 검증 2023-03~2026-03 완료"를 뒷받침하는 산출물이 디렉터리에 없다. 현재 운영 파라미터는 검증 이력이 없다.
재실행 최소 요건 (§8.1-5 를 만족시키려면):
- 국내 2023-03~2026-09 전 구간, BULL/BEAR/SIDEWAYS 각 6개월 이상 포함
- Walk-forward 5구간 이상(Train:Test = 4:1, 앵커드 1회 + 롤링 1회). 판정선: OOS Sharpe / IS Sharpe ≥ 0.5, 구간별 Sharpe 변동계수 ≤ 0.6
- 파라미터당 최소 100거래 → 유효 자유 파라미터 8
10개면 총 8001,000 거래. 미달이면 유니버스 확대보다 파라미터 감축이 정답 - 모드별 거래 수 분리 보고. Aggressive 표본이 5.6%→2.0% 로 줄었으므로 100 미달 가능성이 높다 → 미달이면 "Aggressive 는 통계적으로 검증되지 않음"으로 명시하고 실전 활성화 보류
- 거래비용 재확인(수수료 0.015%×2 + 매도세 0.18% + 슬리피지 0.1~0.3%). RSI 변경으로 진입 타점이 달라져 회전율이 바뀌므로 비용 민감도 재측정
- 몬테카를로(거래 순서 셔플 10,000회) — MDD 95th pctile, §5.3 트리거 빈도 변화
- 미국은 수정 전/후 A-B 로 1봉 정렬 효과를 분리 측정한 뒤 walk-forward
8.4.1 운영 실측 (2026-09-28)
운영 DB 를 직접 조회한 결과. 배포 전/후를 구분해 기록한다.
- 거래 이력:
daytrading257건(BUY 127/SELL 130, 2026-04-06~09-23) vsswing53건(BUY 15/SELL 38). DT 가 거래 건수로는 주력이었다. DT 포지션이 항상 0인 것은 15:20 EOD 전량 청산이 설계대로 동작한 결과이며 결함이 아니다. - 2026-09-24~25 거래·분석 0건은 추석 연휴다.
market_schedule.py에 휴장일 판정이 없어 로그만으로는 장애와 구분되지 않는다. strategy_configs가 옛 값으로 운영되고 있었다 (alembic 0012 배포 전): BULL 진입 RSI 30(config 46), 손절 -6%(-7%), DT 3종목·종목당 10%. RSI<46 후보는 95종목인데 RSI<30 은 32종목 — 후보가 3분의 1 이었다. 2026-09-28 배포(alembic 0011→0012)로 해소됨.- 룰 점수는 0~15 스케일이다(
market_analyzer.py:608이combined_score/100*15로 축소 저장). 실측 분포는 최소 -1 / 최대 6 / 평균 1.4 (199종목). 따라서score >= 60형태의 비교는 구조적으로 충족 불가다. - 배치는 cron 이 아니라 앱 내부 스케줄러가 돌린다(
main.py:269→schedulers.py:74, 10분 간격). NAS 에 crontab 은 등록되어 있지 않다. - 9/23 에
active_trades유령 행 129개(day_trade120 +swing9)를 수동 정리한 백업이 배포 디렉터리에 남아 있다(active_trades_backup_20260923_1036.sql). 전략명 없이 close 해 엉뚱한 행이 닫히던 문제의 증거이며, 이번 세션에서 수정했다.
8.5 미해결 항목 (알려진 문제, 임의로 "고쳤다"고 판단하지 말 것)
2026-09-27 작업으로 이전 목록 11건 + 추가 발견 결함 다수가 해소되었다. 아래는 그 과정에서 새로 드러난 것과 의도적으로 남긴 것이다. 전체 유닛 스위트는
559 passed / 0 failed(11.8초, 소켓 차단 하) 상태다.
백테스트 (최우선)
- 기존 백테스트 산출물 전부 폐기 상태 — 재실행 필요. §8.4 의 최소 요건 7개를 갖춘 새 산출물이 나오기 전까지 어떤 파라미터도 "검증됐다"고 말할 수 없다.
✅ 2026-09-28 정정: 재실행이 가능하다. 이전 판이 "NAS 다운으로 백테스트 불가"라고 적은 것은 오류였다 — NAS(
nas.shium.net, DNS 정상)는 가동 중이고autostock-postgres도 떠 있다. DB 포트가127.0.0.1:15433로 바인딩돼 외부에서 직접 붙을 수 없을 뿐이며, NAS 안에서(docker exec autostock-postgres psql) 또는 SSH 터널로 접근한다. RANGE_TRADE_ENABLED/MEAN_REVERSION_BEAR_V2_ENABLED는 백테스트 통과 전 True 금지. 각 파일 docstring 에 통과 조건이 기재되어 있다. (DB 가 살아 있으므로 실제로 검증할 수 있다.) BEAR v2 는 "반전캔들 없이 %B+거래량으로 통과"가 발화의 53% 이므로 그 버킷만 분리 집계해 PF < 1.0 이면_W_REVERSAL을 필수화하거나_MIN_SCORE를 0.75 로 올려야 한다.- 박스권 전략이 스케줄러에 배선되어 있지 않다. 배선 위치는
swing/scheduler.py의 MDD/AUTO_TRADE_ENABLED게이트 직후(기존 step 7 은 추천 데이터 존재를 전제하므로 SIDEWAYS 에서 도달하지 못한다).swing/exit_service.py에RANGE_TRADE전용 청산 분기도 없어 현재는 평균회귀 청산으로 처리된다 — 박스 상단 익절·박스 이탈 즉시 손절이 적용되지 않는다.
미국 전략 (US_TRADING_ENABLED=False 라 당장 위험은 없으나 켜기 전 필수)
4. us_backtest_engine.py 는 import 자체가 불가하다. check_us_sell_signal 을 import 하는데 us_stock_strategy.py 에 그 함수가 없다(check_us_buy_signal 만 존재). 즉 미국 백테스트는 실행된 적이 없다.
5. us_backtest_engine.US_COMMISSION_RATE = 0.001(0.1%) vs config.US_COMMISSION_RATE = 0.0(0%) — 백테스트와 실전의 수수료가 다르다. §8.4 의 "백테스트가 실전과 다르다" 계열이 미국 쪽에도 있다.
6. US_STOP_LOSS_PCT 는 음수 규약(-7.0)이다. 국내 STOP_LOSS_PERCENT(7.0 양수)와 다르며, us_backtest_engine.py:581 이 pnl_pct <= US_STOP_LOSS_PCT 로 음수 그대로 비교한다. 부호를 바꾸면 백테스트 손절이 "수익률 ≤ +7%" 로 뒤집힌다.
7. us_market_scheduler.US_DEFAULT_FX_RATE(1350.0)는 주문 수량 결정 입력이다(표시값이 아니다). buy_budget_krw / exchange_rate → quantity. 잔고 조회 실패 또는 "USD 자산 0"(신규 계좌) 경로에서 발동하며, 실환율 1450 에 1350 을 쓰면 약 7% 과다 주문이 된다.
8. us_market_scheduler.py 에 남은 하드코딩: 시장 시간(dtime(9,30)/(16,0)/…), 현금 하한 500,000, 최소 주문 $100, 지정가 마진 1.005/0.995, 총자산 폴백 10,000,000. us_stock_strategy.check_us_exit_signal 의 인라인 리터럴(rsi >= 65, bb_upper * 0.99, sell_pct=0.50, remaining_pct > 0.25). round(buy_price * 0.93, 2) 는 1+(-7.0/100)=0.9299999999999999 와 부동소수 비트가 달라 의도적으로 리터럴을 유지했다.
지표 정확성 (전부 SL/신호를 움직이므로 백테스트 동반 필수)
9. atr_sma 는 TR 의 단순평균이며 표준 Wilder ATR 이 아니다. 바꾸면 스윙 SL(진입가−2.5×ATR)·트레일링(고점−1.5×ATR)·DT SL 이 전부 이동한다. 비교 지표는 CAGR 이 아니라 손절 체결 건수·평균 손절폭·whipsaw 비율.
10. determine_adaptive_mode() 의 ATR 백분위가 closes[:i](당일 제외) 기준으로 1봉 지연. 백분위라 영향이 작을 것으로 보이나 확인되지 않았다.
11. run_backtest.calculate_adx_simple 은 본체가 adx_wilder 위임으로 통일됐으나 폴백 계약이 달라 함수 2개로 남아 있다(전자는 float+20.0 폴백, 후자는 Optional[float]).
12. us_backtest_engine._compute_sma 미통합(중복 아님, 유일 구현). _compute_bollinger 는 봉별 슬라이싱으로 O(n²) — 값은 정확하고 성능만 손실.
설정·구조
13. config.py 의 SHORT_TERM_* 키들은 삭제된 전략의 잔재로 아무 효과가 없다(백테스트 폴백에서만 참조).
14. DT 호출부(api/endpoints/day_trading.py, daytrading/entry_service.py)는 예산→주수 변환에 float // 를 쓰고, 스윙/집행은 sizing_core.amount_to_quantity(Decimal)를 쓴다. 이관 시 극단적 경우 1주 차이가 날 수 있어 동일성 재검증이 필요하다. sizing_core.apply_confidence_scale 도 아직 그쪽에 도입되지 않았다.
15. scan_range_trading_signals 가 결과에 action=="BUY" 만 확인하고 success 를 확인하지 않아 주문 실패건이 섞일 수 있다.
16. portfolio.py 의 ActiveTrade ORM UPDATE 경로들은 같은 트랜잭션에서 SELECT 한 행의 수정이라 남겨 두었다. 직접 INSERT 는 코드베이스 전체 0건이다.
17. backend/test_krx.py(tests/ 밖, 백엔드 루트 스크립트)는 인자 없이 pytest 를 실행하면 수집돼 컬렉션 에러를 낸다. pytest tests/ --ignore=tests/integration 로 돌리면 무관. pytest.ini 에 testpaths = tests 로 정리 가능.
18. naver_finance 등 KIS 외 외부 HTTP 경로는 conftest 가 전역 차단하지 않는다. 현재 소켓 차단 하 전체 통과로 실호출은 없음을 확인했으나, 추가 방어가 필요하면 별도 작업.
멀티테넌트
19. MT 2단계 잔여 — 스케줄러 사용자별 순회, legacy 라우터 격리, user_id 를 실제로 payload 에 싣는 호출부 전환, alembic 0013(NOT NULL + uq_portfolio_user_stock). db_worker 의 MultipleResultsFound 전제조건 1건은 해소됐다. MULTI_TENANT_ENABLED=True 는 계속 금지.
ML 앙상블 (2026-09-28 — 코드는 고쳤으나 모델 재학습 전까지 신뢰 금지)
20. 운영 LSTM 체크포인트가 무의미하다. seq_mean=[0,0,0,0,0], seq_std=[1e-8]×5 — X_seq 가 전부 0 인 상태로 학습된 흔적이다(구 prepare_training_data 가 시퀀스에 NaN 을 넣어 X_seq.mean 이 NaN 이 된 결과). std≈0 이면 정규화가 x/1e-8 로 폭주해 가중치 35% 를 가진 LSTM 출력이 사실상 난수다. 시계열 방향 수정 + 시퀀스 NaN 수정이 들어갔으므로 재학습이 필요하다. 재학습 후 새 metrics.json 의 lstm_seq_std_min 과 lstm_per_class 로 판정할 것 — 정확도만 보면 "전부 HOLD" 모델을 통과시킨다.
21. ML_ENSEMBLE_BUY_THRESHOLD(60) 재보정 보류 중. 관측 ensemble_score 1850 이라 ML BUY 가 구조적으로 0건이었다. 그러나 그 분포는 역방향 시계열에서 나온 값이므로 지금 임계값을 조정하면 잘못된 분포에 맞추는 것이 된다. 순서는 ① 재학습 → ② 분포 재측정 → ③ 임계값 판단이고, 값 변경은 §8.1-5(백테스트)를 거친다.
22. ⚠️ 120봉 구간의 학습-추론 정합성은 재학습 후 A-B 로 확인할 것.
26. 종가 0/결측이 중간에 섞인 차트는 피처가 유한해 여전히 점수가 산출된다(NaN 문제가 아니라 데이터 품질 문제). 연속 0봉 비율 등 품질 검증은 별도 작업.trade_executor 의 ML HOLD 게이트가 다시 작동한다. 지난 닷새간 ml_signal=NULL 이라 무력화돼 있었다(§6.4). 이번 수정으로 HOLD & 신뢰도≥60% 조건이 되살아나므로 배포 후 매수 건수가 줄 수 있다. 20번(LSTM 무의미)이 해소되기 전에는 이 게이트가 난수로 매수를 막을 수 있다 — 매수 0건이 재발하면 ml_signal 분포를 먼저 확인할 것.
23. load_models() 는 all-or-nothing. lgbm 로드 실패 한 번으로 scaler/rf/lstm 까지 버리고 is_trained=False 가 된다(로컬에서 libomp 부재로 실제 발생). logger.error 는 남는다. 부분 로드 허용은 정책 결정이라 손대지 않았다.
24. KIS 폴백 경로의 실제 봉수가 미확인이다. get_daily_chart(limit=240) 은 페이지네이션(tr_cont F/M)에 의존하므로 연속조회가 실패하면 80봉 미달로 ML 이 계속 스킵된다. 운영 로그 ✅ Fetched N chart data points 의 N 을 확인할 것. 네이버 경로는 154봉으로 충족(실측).
25. 하드코딩 3건이 남아 있다 — market_analyzer._NAVER_ML_CHART_DAYS=200, _NAVER_LEGACY_DAYS=80, _ML_SKIP_LOG_EVERY=50. config 화가 필요하면 ML_CHART_DAYS/ML_SKIP_LOG_EVERY 로 편입. RECOMMENDED_BARS_FOR_PREDICT=120 은 현재 강제하지 않는다(최소 80만 강제) — RSI(Wilder)·EMA 가 재귀라 80
마지막 업데이트: 2026-09-27 — 죽은 코드 2,695줄 제거 / 지표·사이징 단일화(수치 무변경 증명) / 파라미터 3중 소스 정합 + alembic 0012 / 섹터 매핑 44→747종목 / 백테스트를 실전 로직에 정합(ADX 14배 발산·RSI SMA↔Wilder·1봉 지연) / 미국 상수 13개 config 편입 / 테스트 실 KIS 호출 차단(conftest 신설) / 유닛 스위트 559 passed·0 failed
2026-09-28 — 운영 파라미터 alembic 0012 배포 완료(RSI 30→46, 손절 -6%→-7%) / ML 앙상블 전면 미작동 수정(시계열 역방향·시퀀스 워밍업 NaN·except Exception: pass) + 회귀 테스트 20건 / 유닛 스위트 579 passed·0 failed. ML 은 재학습 전까지 신뢰 금지(§8.5-20).