본문으로 건너뛰기

비상 정지

quant-ai는 다섯 단계 비상 정지 절차를 갖습니다 — 가장 빠른 광역 차단부터 세밀한 사용자 단위 잠금까지. 상황별로 가장 작은 단위에서 큰 단위로 넘어가는 것이 원칙입니다.

비상 정지 단계

단계영향 범위트리거해제
0자동: 사용자 × 자산군일일 손실 -5%24h 자동 잠금, 운영자 재활성화
1사용자 1인 라이브user_settings.live_enabled[ac]=false즉시
2자산군 전체 라이브FEATURE_EQUITY_LIVE=false (compose env)즉시
3모든 라이브ALLOW_LIVE=0 (VM 호스트 env)즉시
4모든 거래 (페이퍼 포함)API 컨테이너 정지수동 재기동

0. 자동 kill switch

트리거

-- 사용자 × 자산군 별 일일 PnL%
WITH per_user AS (
SELECT user_id,
asset_class,
COALESCE(SUM(realized_pnl), 0) + COALESCE(SUM(unrealized_pnl), 0) AS daily_pnl,
GREATEST(SUM(ABS(entry_price * size)), 1) AS notional
FROM positions
WHERE COALESCE(closed_at, opened_at) >= date_trunc('day', NOW())
GROUP BY user_id, asset_class
)
SELECT user_id, asset_class, daily_pnl / notional * 100.0 AS pct
FROM per_user
WHERE daily_pnl / notional * 100.0 <= -5.0;

해당 사용자 × 자산군의 BotManager 인스턴스가 emergency_stop()을 호출 받고:

  1. 모든 미체결 주문 취소
  2. (옵션) 보유 포지션 시장가 청산
  3. bot_states 테이블에 emergency_stop_until = now + 24h 기록
  4. Telegram 알림 발송 (사용자 + ops 양쪽)

임박 알림 (-4%)

Daily Loss Imminent Grafana 룰이 -4% 도달 시 critical 알림을 Telegram + 이메일로 보냅니다. 이 시점에 운영자가 다음을 결정할 수 있습니다.

  • 사용자에게 자발적 정지 권유
  • 단계 1로 라이브 비활성화

해제

24h 자동 만료 또는 운영자 수동:

# 사용자 × 자산군 잠금 해제
docker compose exec timescaledb psql -U quant -d quantai -c "
UPDATE bot_states
SET emergency_stop_until = NULL
WHERE user_id = <USER_ID> AND asset_class = '<us_equity|kr_equity|crypto>';
"

# (옵션) UserSettings도 재활성화 — 자동 만료 후라면 자동으로 다시 가능
docker compose exec timescaledb psql -U quant -d quantai -c "
UPDATE user_settings
SET live_enabled = jsonb_set(live_enabled, '{us_equity}', 'true'::jsonb)
WHERE user_id = <USER_ID>;
"
24h가 가짜 윈도우는 아니다

24h 잠금은 사용자에게 손실 회복 충동(revenge trading)을 막기 위한 의도적 쿨다운입니다. 명백한 시스템 버그 (잘못된 PnL 계산 등)가 원인이 아닌 한, 운영자가 24h 안에 해제하지 않습니다.

1. 사용자 1인 라이브 차단

특정 사용자의 의심 행동 / 격리 사고:

# 즉시 비활성
docker compose exec timescaledb psql -U quant -d quantai -c "
UPDATE user_settings
SET live_enabled = jsonb_build_object(
'us_equity', false,
'kr_equity', false,
'crypto', false)
WHERE user_id = <USER_ID>;
"

# 진행 중 봇 매니저도 강제 정지 (admin API)
curl -X POST -H "Authorization: Bearer <ADMIN_JWT>" \
http://localhost:8000/api/admin/users/<USER_ID>/bot/stop

2. 자산군 전체 라이브 차단

특정 브로커 5xx 폭주 등 자산군 단위 인시던트:

# .env에서 해당 브로커만 OFF
sed -i 's/^FEATURE_ALPACA_BROKER=.*/FEATURE_ALPACA_BROKER=false/' .env
# 또는
sed -i 's/^FEATURE_KIS_BROKER=.*/FEATURE_KIS_BROKER=false/' .env

docker compose restart api analysis-worker

3. 모든 라이브 차단 (가장 빠름)

VM 호스트 env만 끄면 됩니다 — .env 안의 FEATURE_EQUITY_LIVE는 그대로 두어도 안전합니다 (이중 잠금).

ssh azureuser@<vm-ip> '
cd ~/quantai
sed -i "s/^ALLOW_LIVE=.*/ALLOW_LIVE=0/" .env
sudo -E docker compose -f docker-compose.prod.yml restart api analysis-worker position-reconciler
'

# 검증
ssh azureuser@<vm-ip> 'curl -s http://localhost:8000/health/flags | jq .FEATURE_EQUITY_LIVE'
# false

해제는 ALLOW_LIVE=1로 되돌리고 같은 컨테이너 재시작.

4. 모든 거래 차단 (최후)

API 컨테이너 자체를 정지합니다. 페이퍼 / 라이브 / 분석 / 사용자 로그인까지 전부 503이 됩니다.

docker compose stop api

# 진행 중 거래소 주문이 있다면 사용자 안내 후
# Grafana — Broker Health 에서 미체결 주문 청산 여부 확인

재기동:

docker compose start api
# /health 200 확인 후 정상 동작
curl -fsS http://localhost:8000/health

인시던트 워크플로우

flowchart TD
A[알림 수신<br/>Telegram] --> B{영향 범위?}
B -->|사용자 1인| C[Step 1: live_enabled=false]
B -->|자산군 전체| D[Step 2: FEATURE_*_BROKER=false]
B -->|모든 라이브| E[Step 3: ALLOW_LIVE=0]
B -->|시스템 광역| F[Step 4: API 정지]
C --> G[Grafana 진단]
D --> G
E --> G
F --> G
G --> H[패치 / 재개]

사후

비상 정지 후에는 항상:

  1. 인시던트 티켓 (severity / scope / actions)
  2. 영향 사용자에게 Telegram + 이메일 사후 안내
  3. 5 Whys 포스트모템 (24h 내)
  4. 재발 방지 PR (테스트 / 알림 룰 / 임계 조정)

검증

# 현재 라이브 활성 여부
curl -s http://localhost:8000/health/flags | jq .

# 사용자별 emergency_stop 잠금 상태
docker compose exec timescaledb psql -U quant -d quantai -c "
SELECT user_id, asset_class, emergency_stop_until
FROM bot_states
WHERE emergency_stop_until > NOW();
"

# 미체결 주문 / 보유 포지션 잔량
docker compose exec timescaledb psql -U quant -d quantai -c "
SELECT user_id, asset_class, COUNT(*) AS open_positions
FROM positions WHERE closed_at IS NULL
GROUP BY user_id, asset_class;
"

트러블슈팅

증상원인 / 조치
ALLOW_LIVE=0인데 라이브 주문이 들어감API 재시작 누락. docker compose restart api
Step 1 후에도 봇이 주문 시도BotManager가 메모리 캐시에 가지고 있음. admin API로 명시적 stop 호출
잠금 해제 후에도 첫 주문이 거부24h 윈도우가 아직 진행 중. bot_states.emergency_stop_until 확인
Telegram 알림이 폭주Grafana repeat_interval을 늘리거나 임시로 룰 mute
reconciler가 정지된 봇에서도 액션정상. 리컨실러는 broker vs DB 정합성을 위해 bot 상태와 무관하게 동작 → 포지션 리컨실러

관련 페이지