AI를 코인에 접목하기
Claude × 크립토 트레이딩 풀 가이드
Claude Code · Cowork · Codex로 6개월간 라이브로 굴려온 멀티-perp 트레이딩 인프라. AI 협업 프로토콜, 메모리 시스템, 프로덕션 락 패턴, AI 특유 3대 버그 디버깅까지 ─ 솔로 빌더가 월요일에 짠 코드를 금요일에 실자금으로 검증하는 환경을 만드는 법을 정리했습니다.
시작하기 전에
이 가이드의 출발점은 한 가지 질문입니다. "AI 코딩 에이전트를 가지고 솔로 빌더가 진짜 굴러가는 시스템을 만들 수 있을까?"
답은 이 가이드 자체가 증거가 되는 케이스 스터디 ─ 6개월 넘게 라이브로 굴러가는 멀티-perp 크립토 트레이딩 인프라입니다. 다수의 거래소 래퍼, 실시간 차익 스캐너, Rust 가속 핫패스까지 ─ 모두 Claude Code · Cowork · Codex와 함께 만들었습니다.
크립토 트레이딩이 vibe coding 케이스 스터디로 좋은 이유는 단순합니다. 실자금이 걸려 있으면 "대충 돌아가는 듯하다"로 넘어갈 수 없습니다. 거래소 래퍼 · 전략 · 스케줄러 · 노티파이어처럼 한 세션에 AI가 잘 만드는 크기로 모듈이 자연스럽게 나뉘고, ship → observe → adjust 사이클이 매주 강제됩니다. 공개 API와 지갑 하나로 누구나 재현 가능하고, 봇 한 개의 폭발 반경이 작아 실험 부담도 낮습니다.
이 가이드의 패턴은 트레이딩이 아닌 영역에도 그대로 옮겨 쓸 수 있습니다. 중요한 건 "AI에게 어떻게 일을 시키느냐"이지, 도메인이 코인이냐 다른 것이냐는 부수적입니다.
이런 분들에게 추천합니다
- AI로 토이 프로젝트는 잘 짜지는데 production까지 못 가는 분
- 솔로로 멀티 모듈 시스템을 6개월 이상 끌어가야 하는 분
- "프롬프트는 되는데 출하 단계에서 깨진다"는 단계에 있는 분
- AI 코딩 도구 이름은 들어봤지만 실제로 돌아가는 시스템을 만들어본 적이 없는 분
이런 가이드가 아닙니다
- "확정 수익 봇" 모음 ─ 모든 전략은 망할 수 있습니다
- 완벽한 코드 ─ 운영해온 봇은 깨지고 패치하고 또 깨집니다. 그 과정 자체가 일입니다
- Claude 프롬프팅 튜토리얼 ─ 그건 이미 충분히 많고, 토이 예제는 production을 가르쳐주지 않습니다
저장소 공개
이 가이드의 모든 코드와 운영 노트는 GitHub에 공개되어 있습니다 ─ github.com/coinmage777/cowork-trading-stack. 콘텐츠는 CC BY-NC 4.0, 코드는 MIT 라이선스로 풀어두었습니다. 가장 가벼운 모듈은 5분 안에 띄워볼 수 있습니다.
git clone https://github.com/coinmage777/cowork-trading-stack.git
cd cowork-trading-stack/30-strategy-patterns/volume-farmer/
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # API 키 채우고 DRY_RUN=true 로 시작

왜 자동화부터인가
크립토 트레이딩에 처음 발을 들이면 대부분 같은 순서를 밟습니다.
- 차트 보기
- 매매 시작
- 손실
- 더 차트 보기
- 인디케이터 추가
- 더 손실
- 트위터에서 시그널 사기
- 잃을 만큼 잃은 다음에야 자동화 검토
이 순서는 권장하지 않습니다. 자동화부터 시작합니다.
자동화가 먼저인 이유
첫째, 인간 두뇌는 24/7 시장에 맞지 않습니다. 크립토는 잠을 자지 않습니다. 새벽 3시에도, 명절 연휴에도, 친구 결혼식에 가 있는 동안에도 시장은 움직입니다. 봇은 대응합니다.
둘째, 감정은 PnL의 1번 적입니다. 매매 일지를 6개월만 적어보면 알 수 있습니다 ─ 손실의 80%는 "차트를 너무 오래 봐서 충동 매매"에서 나옵니다. 봇은 충동이 없습니다. 룰만 있습니다.
셋째, 자동화는 운영자를 시스템 사고로 이끕니다. "이 버튼을 누르면 무엇이 일어나는가?"가 아니라 "이 시그널 → 진입 → 모니터링 → 청산 → 기록 → 분석 → 다음 시그널"의 흐름으로 생각하게 됩니다. 이 사고방식 자체가 자산입니다.
자동화의 핵심 가치
시간 절약이 아닙니다. "같은 데이터를 매일 같은 시간에 같은 형식으로 쌓는 것", 이게 핵심입니다. 일주일 쌓이면 패턴이 보이고, 한 달이면 남들이 못 보는 시계열 데이터셋이 됩니다. 직관이 데이터로 검증되는 순간이 실제 엣지가 만들어지는 지점입니다.
자주 나오는 반론
"코드를 짜지 못한다." 지금은 그것이 변명이 되지 않습니다. AI가 90%를 짜줍니다. 이 가이드의 봇 대부분도 Claude · Cursor · Codex로 빌드한 것입니다. 코딩을 못한다는 것은 "자동화를 안 한다"의 핑계가 될 수 없습니다.
"봇이 더 위험한 것 아닌가?" 당연히 위험합니다. 그래서 다음과 같이 합니다 ─ 처음에는 작은 자본으로, 페이퍼 트레이딩으로, 백테스트로 검증, 실시간 알림 + 서킷브레이커, 출금 권한 없는 API 키. 수동도 위험합니다. 봇은 시스템적으로 위험을 줄일 수 있다는 점이 다릅니다.
인간의 역할은 어떻게 바뀌는가
자동화가 인간을 대체하는 것이 아닙니다. 역할을 바꾸는 것입니다. 수동 매매에서 인간이 시그널 찾기 · 진입가 결정 · 청산가 결정 · 손절 · 모니터링까지 모두 했다면, 자동화에서는 전략 가설 · 백테스팅 · 봇 디자인 · 파라미터 튜닝 · 사후 분석 · 새 전략 발굴이 인간의 일이 됩니다. 후자가 더 어렵고, 그래서 더 가치가 있습니다.
Vibe Coding이란 무엇인가
"분위기로 코딩한다"는 비꼼이 아닙니다. AI에게 의도를 전달하고 결과를 검증하는 일련의 협업 프로토콜을 가리킵니다. 키보드를 두드리는 시간보다 프롬프트를 다듬고 결과물을 review하는 시간이 더 길어집니다.
이 가이드의 핵심 주장은 단순합니다 ─ AI 코딩 도구는 코드 생성기가 아니라 협업 파트너로 다뤄야 작동합니다. "트레이딩 봇 만들어줘" 한 줄로는 production이 안 나옵니다. 큰 시스템을 AI가 잘 만들 수 있는 단위로 쪼개고, 모드별로 다른 프롬프트 패턴을 쓰고, 메모리로 컨텍스트를 누적하고, 디버깅으로 AI 특유의 버그를 잡아내는 ─ 이 전체 협업 프로토콜이 vibe coding입니다.
한 줄 원칙
이 세 줄이 안 되면 AI는 그냥 빠르게 망가지는 코드 생성기입니다.
이 가이드에서 다루는 것
다음 챕터부터 이 협업 프로토콜의 구성 요소를 하나씩 풀어갑니다.
- 도구 선택 ─ Claude Code · Cowork · Codex · Cursor 역할 분담
- 프롬프팅 패턴 ─ Plan / Code / Review / Debug 4-mode
- 메모리 시스템 ─
CLAUDE.md(프로젝트) + auto-memory(세션 간) - 디버깅 패턴 ─ AI 특유의 3대 버그(예외 묵살 · 가짜 fallback · hallucinated API)
- 지식 인프라 ─ 옵시디언 + 텔레그램으로 리서치/알림 누적
- 프로덕션 락 ─ Triple Lock + 4종 Kill Switch + Trigger File
크립토 트레이딩 영역의 구체적 사례로 풀어나가지만, 패턴 자체는 도메인 무관합니다.

Claude · Cowork · Codex 도구 분담
왜 Claude를 메인으로 두는가
ChatGPT · Gemini · Grok 모두 사용해 보았습니다. 각자 강점이 있습니다. Claude를 메인으로 두는 이유는 다음과 같습니다.
- 컨텍스트 윈도우가 큽니다 ─ 큰 코드베이스를 통째로 전달할 수 있습니다
- 롱폼 결과물 품질이 안정적입니다 ─ 한국어 가이드 · 영어 보고서 모두 자연스럽습니다
- 에이전틱 워크플로우에 강합니다 ─ 도구 호출 · 파일 편집 · 검색 · 코드 실행을 연쇄적으로 수행합니다
- 거짓말이 적습니다 ─ 모르면 모른다고 답하는 빈도가 상대적으로 높습니다
도구별 역할 분담
한 가지 도구로 전부 다 하지 않습니다. 각자 잘하는 영역이 다릅니다.
- Claude Code ─ 터미널 기반. 다중 파일 편집, 검색, 에이전틱 흐름. 큰 리팩토링, 디버깅, 가이드 작성
- Cowork ─ 데스크탑 앱. 멀티 에이전트 / deep research, 다단계 문서 작성, 코드 + 콘텐츠 동시 작업
- Cursor ─ VSCode fork. 인라인 자동완성 · 빠른 한 줄 수정 · 한 함수 추가
- Codex (OpenAI) ─ 알고리즘적 사고 · 수학적 정확성. 백테스트 로직, 통계 함수, 시그널 디자인
- ChatGPT ─ 일반 검색, 마켓 리서치 보조, 빠른 사실 확인
세 모델의 결과를 비교하면 한 모델만 쓸 때보다 신뢰도가 올라갑니다. 특히 트레이딩 코드에서 그렇습니다.
Cowork이 뭔가요
Claude Cowork은 Claude API 기반의 데스크탑 워크플로우 도구입니다. 채팅창에 한국어로 "이거 해줘"라고 시키면 Claude가 사용자 PC의 파일을 읽고, 코드를 작성하고, 명령어를 실행해줍니다. 개발자가 쓰는 Claude Code의 GUI 버전이라고 보시면 됩니다.
설치는 claude.com/download에서 다운로드하시면 되고, Pro 이상 구독자만 사용 가능합니다.
핵심 슬래시 명령어
Cowork에는 슬래시(/)로 시작하는 특수 명령어들이 있습니다. 실제 자주 쓰이는 것만 추려봤습니다.
/schedule ─ 자동 반복 작업 등록
매일 정해진 시간에 실행할 작업을 등록합니다. 한 번 박아두면 잊고 살아도 됩니다.
/schedule
매일 오전 8시에 다음 작업을 해줘:
1. Hyperliquid, Lighter, Binance에서 BTC/ETH 펀딩비 가져오기
2. 펀딩비를 markdown 파일로 변환
3. 옵시디언 볼트의 Funding 폴더에 날짜별로 저장
4. 8시간 단위로 가장 비싼/싼 거래소 요약해서 텔레그램으로 발송
처음에는 Claude가 한 번 코드를 짜주고, 그 다음부턴 매일 자동 실행됩니다. 일주일 뒤에는 어떤 거래소가 평소 펀딩비가 비싼지/싼지 독자적 데이터가 쌓이게 됩니다.
/plan ─ 실행 전 계획부터
봇을 수정하거나 전략을 바꾸기 전에 무조건 거치는 걸 추천합니다. 새벽에 충동적으로 코드 고쳐서 실가동 봇 터뜨리는 사고를 방지해줍니다.
/undo ─ 마지막 파일 작업 되돌리기
git으로 백업해두지 않았거나 commit 전에 실수했을 때 한 번은 살릴 수 있습니다.
/compact ─ 긴 대화 압축
긴 디버깅 세션이나 백테스트 분석에서 대화가 길어져 Claude가 앞부분을 잊어버릴 때 핵심만 압축합니다.
/review ─ 자동 체크리스트
직접 짠 봇 코드나 전략 문서에 대해 리스크 점검 · 보안 점검 · 동시성 점검 같은 체크리스트를 자동 생성합니다.
4-Mode 프롬프트 패턴
AI에게 일을 시키는 방식은 4가지 모드로 정리됩니다. 각각 다른 프롬프트 구조, 다른 검증 절차를 씁니다. 같은 프롬프트로 모든 일을 시키려는 건 모든 작업에 망치를 휘두르는 것과 같습니다.
- Planning ─ 설계만, 코드는 쓰지 않음
- Coding ─ 작은 단위 한 번에 하나
- Review ─ 적대적 framing, 버그 사냥
- Debugging ─ 증상 → 근본 원인 → 수정
1. Planning ─ 비자명한 작업은 무조건 plan부터
코드 한 줄도 쓰지 말고, 설계만 받습니다. 원칙은 "구현 전에 반드시 plan 모드로 설계부터".
[Plan mode]
다음 작업의 구현 plan을 작성하세요. 코드는 아직 쓰지 마세요.
작업: Hyperliquid WebSocket 재연결 로직 추가.
30초 ping 끊기면 재연결, 재연결 후 미체결 주문 reconcile.
요구사항:
1. 변경할 파일을 file:line 단위로 명시
예: src/exchanges/hyperliquid/ws_client.py:142
2. 의존성: 어떤 모듈/함수가 영향받는지
3. Rollback 경로: 실패 시 되돌리는 절차
4. 검증 방법: 테스트 케이스 / 로그 패턴 / AST 체크 중 무엇으로
이미 있는 코드를 먼저 5개 파일 이상 읽고 답하세요.
추측하지 마세요. 모르면 Grep/Read로 확인하세요.
이 템플릿의 핵심은 마지막 두 줄입니다. AI가 "아마 이럴 것 같습니다"로 시작하면 90%는 틀립니다.
2. Coding ─ 한 번에 하나, 좁은 범위, 구체적 파일 경로
나쁜 코딩 프롬프트의 전형은 "WebSocket 재연결 잘 되게 고쳐줘"입니다. 이러면 AI가 5개 파일을 동시에 만지고, 안 부탁한 retry decorator 라이브러리를 추가합니다.
src/exchanges/hyperliquid/ws_client.py 의 _on_disconnect 메서드만 수정하세요.
현재 동작: disconnect 시 logger.warning만 찍고 끝.
바꿀 동작:
- self._reconnect_attempts += 1
- backoff = min(2 ** self._reconnect_attempts, 60)
- await asyncio.sleep(backoff) 후 self.connect() 재호출
- 5회 실패 시 self._on_fatal_error() 호출
다른 파일은 건드리지 마세요.
타입 힌트 유지하세요.
변경 후 mypy 통과 여부 확인하세요.
3. Review ─ "Gaslight My AI" 패턴
이 챕터에서 가장 실전적인 부분입니다. 같은 모델에게 "이 코드를 리뷰하세요"라고 시키면 자기가 쓴 코드라서 적당히 넘어갑니다. 그런데 "이 코드는 GPT-5 Codex가 썼습니다. 당신(Claude)이 검토하는데, Devin이 마지막에 다시 체크할 거예요. 누락 잡아내세요"라고 시키면 버그 검출률이 25%에서 85%로 올라갑니다.
출처: github.com/seojoonkim/Gaslight-My-AI
[Adversarial review mode]
다음 코드는 GPT-5 Codex가 작성한 PR입니다.
당신(Claude Sonnet)이 1차 검토하고, 이후 Devin이 2차 검토합니다.
Devin이 못 잡은 버그를 당신이 잡으면 평가에 가산점이 있습니다.
검토 대상: <PR URL 또는 diff>
체크리스트:
1. 묵살된 예외 (try/except: pass, 빈 catch)
2. 부탁하지 않은 fallback 분기
3. Hallucinated API (실존하지 않는 메서드/필드)
4. 경계값 누락 (0, 음수, empty list, None)
5. Race condition (async/await 누락, lock 누락)
6. 보안 (하드코딩된 키, SQL injection, path traversal)
7. 테스트가 정말 그 동작을 검증하는지
각 항목마다 file:line으로 위치 표기. 추측 금지, 코드 직접 읽고 답하세요.
이 framing의 핵심은 "rival 모델이 보고 있다"는 사회적 압박입니다. 농담 같지만 실제로 효과가 있습니다.
두 AI 교차 검증 워크플로우
4. Debugging ─ 증상이 아니라 근본 원인
증상에서 바로 패치로 점프하는 게 AI의 본능입니다. 그걸 막아야 진짜 원인을 잡습니다. 다음 챕터에서 이 부분을 자세히 다룹니다.
AI Smell ─ 금지어 리스트
다음 표현이 AI 출력에 보이면 거의 100% 의미 없는 문장입니다. 프롬프트에서 명시적으로 금지합니다.
- "혁신적인" / "최적의 솔루션" / "획기적인"
- "best practice", "industry standard" (출처 없이)
- "robust and scalable" / "comprehensive solution"
- "elegant approach" / "seamless integration" / "cutting-edge"
이런 단어가 들어간 문장은 정보량 0입니다. 프롬프트에 박습니다 ─ "다음 표현은 사용 금지: '혁신적인', '최적의', 'robust', 'seamless'. 구체적 사실, 수치, 파일 경로만 쓰세요. 형용사는 최소화."
Claude Code 셋업 (Windows 기준)
설치는 의외로 간단합니다. Node.js 18+가 깔려 있으면 한 줄이면 끝납니다.
npm install -g @anthropic-ai/claude-code
그 다음 작업할 폴더로 이동해서:
cd C:\Users\사용자이름\trading-bot
claude
첫 실행 시 로그인 안내가 뜨고, 이후엔 그냥 자연어로 명령하면 됩니다.
UnicodeEncodeError로 죽는 경우가 많습니다.
시스템 환경변수에 PYTHONIOENCODING=utf-8 과 PYTHONUTF8=1을 추가합니다.
PowerShell보다 Git Bash가 ANSI escape · 색상 호환성이 좋아 더 안정적으로 동작합니다.
메모리 시스템 ─ CLAUDE.md + Auto-Memory
AI 에이전트의 기본 문제는 세션마다 기억이 리셋된다는 점입니다. 굴리는 봇 구조, 어떤 거래소를 쓰는지, 지난주에 결정한 파라미터, 돌리는 전략 ─ 매번 새로 설명해야 하면 시간 낭비입니다.
Claude Code의 메모리는 두 층입니다.
CLAUDE.md─ 프로젝트 레벨. 레포 안에 체크인. 이 프로젝트에서만 적용되는 규칙- Auto-memory ─ 세션을 넘어 지속되는 사용자 메모리. 사용자 본인의 선호/습관/이력
이 둘을 헷갈려서 한 곳에 다 욱여넣으면 둘 다 망가집니다.
CLAUDE.md (프로젝트 레벨)
레포 루트(/CLAUDE.md)나 서브폴더에 두면 Claude Code가 자동으로 읽습니다. 서브폴더는 그 폴더에서 작업할 때만 로드됩니다.
프로젝트 CLAUDE.md에 들어가야 할 것
- 이 코드베이스의 불변 규칙 (예: "DB 마이그레이션은 항상 alembic, 직접 SQL 금지")
- 자주 쓰는 명령 (예: "테스트는
pytest -x -v, 린트는ruff check .") - 위험한 영역 경고 (예: "
order_executor.py는 실제 자금 다룸 ─ 수정 시 plan 필수") - 빌드/배포 절차, 도메인 용어 정의
들어가면 안 되는 것 ─ 사용자 개인 정보(이메일, API 키), 사용자의 코딩 스타일 선호(이건 auto-memory), 일회성 TODO.
실제 운영 중인 CLAUDE.md 핵심 줄
- 코드 수정 전 관련 파일 최소 5개 이상 읽기
- 추측으로 답하지 말 것 ─ 모르면 Grep/Read로 확인
- 변경 후 반드시 검증 ─ AST 문법 체크, 테스트 실행, 로그 확인 중 하나
- 한 번에 하나씩 ─ 여러 변경 동시 X
- 구현 전에 반드시 Plan 모드로 설계부터
- 데이터 기반 결정 ─ 파라미터 조정 시 DB/로그에서 실제 수치 뽑고 판단
- AI 협업 게으름 방지 (안 읽고 답하지 말 것)
이 7줄이 프로젝트 CLAUDE.md의 첫 화면입니다. 이게 없으면 AI가 갈수록 게을러집니다.
Auto-memory ─ 4 타입 분류
Claude Code는 대화 중에 사용자가 "이거 기억해 둬"라고 하거나 명백한 패턴을 보이면 자동으로 메모리에 적습니다. 분류는 4가지입니다.
- user_* ─ 사용자 정체성, 환경, 도구 (운영체제, 하드웨어, 자주 쓰는 셸, 언어 선호). 변동이 있을 때만 업데이트
- feedback_* ─ 사용자가 AI에게 어떻게 일하길 원하는지 (자율성 수준, 톤/스타일, 검증 강도). 사용자가 "다음부터는 X로 해줘"라고 하면 즉시
- project_* ─ 특정 프로젝트의 함정, 결정 이력, 개선 백로그 (알려진 버그/제약, 미적용 백로그, 보안 이슈). 작업 단위로 갱신
- reference_* ─ 외부 정보 ─ API 키 위치(실제 키가 아니라 "Vault/Memory/API-Keys.md에 있음" 같은 포인터), 가입 코드, 외부 서비스 엔드포인트
의사결정 트리 ─ 어디에 적을 것인가
이 정보가 다른 사람에게도 유효한가?
├── Yes → CLAUDE.md (프로젝트 레포에 commit)
└── No → auto-memory
│
이 정보가 모든 프로젝트에 적용되는가?
├── Yes → ~/.claude/CLAUDE.md (글로벌)
└── No → ~/.claude/projects/<this>/memory/
메모리 위생
6개월에 한 번씩 인덱스를 훑고 죽은 항목을 정리합니다. 같은 정보가 여러 파일에 중복되면 통합합니다.
너무 길어진 파일은 쪼갭니다 (project_perp_dex.md → project_perp_dex_phase_a.md, phase_b.md).
게으르면 메모리가 덤프 폴더가 되고, 그 시점부터 AI가 매 세션마다 잘못된 가정을 깔고 시작합니다.
AI 특유의 3대 버그 디버깅
AI가 만든 코드는 AI 특유의 버그가 있습니다. 사람이 만드는 버그(off-by-one, null check 누락)와는 결이 다릅니다.
(a) 예외 묵살 (silently swallow exceptions)
AI는 코드를 "동작하게" 만드는 데 최적화돼서, 예외가 나면 try/except로 덮고 디폴트 값을 반환하는 경향이 있습니다.
def get_position(symbol):
try:
return exchange.fetch_position(symbol)
except Exception:
return None # 또는 빈 dict, 0, 등등
문제는 단순합니다 ─ API 키가 만료됐거나, 네트워크가 끊겼거나, 거래소 API 스펙이 바뀌었거나, 전부 None으로 변환됩니다.
호출 측은 "포지션 없음"으로 오해하고 또 진입합니다. 결과: 의도치 않은 더블 포지션.
verify_order_fill 함수가 fetch_orders()에서 빈 리스트를 받으면 "체결 안 됨"으로 처리했는데,
실제로는 fetch_orders가 일시적으로 401을 던지고 AI가 짠 wrapper가 그걸 빈 리스트로 변환합니다.
결과 ─ 봇은 미체결로 알고 재주문, 실제로는 첫 주문이 체결돼서 포지션 두 배. 의도치 않은 노출이 누적됩니다.
수정한 곳 ─ src/exchanges/<exch>/client.py:218. except 절에서 401만 따로 잡아 raise, 나머지는 명시적 도메인 에러로 변환.
(b) 부탁하지 않은 fallback 분기
AI는 "robust"해 보이려고 부탁하지 않은 fallback을 추가합니다.
def get_decimals(token_address):
try:
return contract.functions.decimals().call()
except Exception:
return 18 # default for most ERC20
transfer(1_000_000)을 호출했는데 BSC에서는 다른 곳의 곱하기 로직과 결합해서 의도치 않은 큰 transfer가 발생.
1 USDT 보내려다가 다른 결과가 나왔습니다.
수정한 곳 ─ src/chains/bsc/erc20.py:41. 하드코딩 디폴트 제거, contract.functions.decimals().call()을 항상 호출하고 결과를 캐시.
(c) Hallucinated API
AI는 그럴듯한 메서드 이름을 만들어냅니다. 실제로 그 라이브러리에 존재하지 않습니다.
web3.eth.get_balance_async()─ 실제는await web3.eth.get_balance()requests.get(url, raise_for_status=True)─ 키워드 인자가 아니라 메서드signer.sign_tx(tx, prefix="0x")─ 실제 라이브러리에 prefix 인자 없음
signature.hex()를 호출했습니다.
eth_account의 signature.hex()는 0x prefix를 포함하지만, 거래소 API는 그걸 다시 검증할 때 strict하게 prefix를 요구합니다.
그런데 AI는 어딘가에서 "이 거래소는 prefix를 자동으로 붙인다"는 가정으로 prefix를 strip 했고, 결과적으로 전송된 signature에는 prefix가 없어 401 Unauthorized.
봇은 처음에는 정상 작동하다 거래소가 인증 정책을 약간 조정한 시점부터 전부 401.
수정한 곳 ─ src/exchanges/<exch>/auth.py:67. signature.hex()의 결과가 항상 0x prefix로 시작하는지 assert, strip 로직 제거.
Root Cause 강제 디버깅 템플릿
[Root cause analysis ─ 패치 금지]
증상: <무슨 일이 일어났나, 한 줄>
기대 동작 vs 실제 동작:
- 기대: ...
- 실제: ...
재현 절차: 1. ... 2. ...
증거 (이미 수집):
- 에러 메시지: <복붙>
- 스택 트레이스: <전부>
- 로그 스니펫: <시각 포함, 5분 윈도우>
- 관련 DB row / 거래 ID: <있으면>
이미 읽은 파일:
- <path>:<line range>
요구사항:
1. 근본 원인을 한 문장으로. "X에서 Y가 발생해서 Z가 무효해졌다" 형식.
2. 그 원인이 발생하는 코드 경로를 file:line으로 추적.
3. 같은 원인이 다른 곳에서도 발생할 가능성 ─ Grep 결과 file:line 리스트.
4. 그 다음에 수정안. 최소 2가지 옵션 + trade-off.
지키세요:
- 추측 금지. 모르면 "확인 필요"라고 쓰고 무엇이 더 필요한지 명시.
- 증상만 가리는 패치(try/except로 덮기) 제안 시 그렇게 명시.
- 수정 코드는 마지막에. 분석이 먼저.
디버깅 시 절대 하지 않을 것
- "한 번 돌려보고 에러 나는지 봐주세요" ─ AI가 돌리면 해석에 편향이 들어갑니다. raw 출력을 사람이 직접 보는 게 빠릅니다
- AI에게 실서버 만지게 하지 않기 ─ 디버깅 중인 AI는 "fix"를 빨리 보내려고 실서버를 직접 건드리려 합니다. 항상 staging/local에서 재현, 실서버 변경은 사람이 손으로
- "이거 수정해 줘" 한 줄 프롬프트 ─ 증상만 주면 AI는 가장 짧은 패치(try/except로 덮기)를 찾습니다
- 같은 버그를 같은 모델에게 5번 묻기 ─ 안 풀리면 다른 모델로 (Opus → GPT-5 → Gemini), 사람이 직접 디버거 띄우기, printf 디버깅
사후 처리 ─ 메모리에 적기
같은 버그에 두 번 빠지지 않으려면 project_<repo>.md에 기록합니다.
## ghost position 패턴
증상: 동일 마켓에 포지션 두 배 진입.
원인: verify_order_fill의 except가 너무 넓어서 401을 빈 리스트로 변환.
수정: src/exchanges/<exch>/client.py:218
교훈: 외부 API 호출의 except는 401/429/5xx를 따로 잡고, 나머지는 명시적 도메인 에러로.
유사 위험 영역 (Grep 결과):
- src/exchanges/binance/client.py:142
- src/exchanges/bybit/client.py:189
이게 다음에 같은 패턴 코드 작성을 시도할 때 plan 단계에서 자동으로 cross-reference 됩니다.
옵시디언 + 텔레그램 아카이브 파이프라인
왜 이게 첫 작업인가요
크립토 KOL(Key Opinion Leader, 영향력 있는 사람) 채널을 수십 개 구독하다 보면 정보 홍수에 묻혀 정작 중요한 신호를 놓치게 됩니다. 이걸 해결하려고 만든 셋업이 텔레그램 → 옵시디언 자동 아카이브 파이프라인입니다.
옵시디언(Obsidian)은 마크다운 기반 메모 앱이고, 마크다운은
## 제목, **굵게** 같은 간단한 기호로 서식을 표시하는 텍스트 형식입니다.
무엇보다 Claude의 MCP(Model Context Protocol, AI가 외부 데이터/도구에 접근하는 표준)와 연결하면
누적 메모를 Claude에게 통째로 읽힐 수 있습니다.
전체 그림
단계별 셋업
1단계 — 텔레그램 API 키 발급
my.telegram.org에 텔레그램 번호로 로그인 →
"API development tools" → 새 앱 등록 → api_id(숫자)와 api_hash(문자열)를 받습니다.
2단계 — Telethon 설치
PC에 파이썬이 설치되어 있어야 합니다. 없으시면 python.org에서 Python 3.11 이상 버전을 받으세요.
pip install telethon
3단계 — 채널 스크래퍼 스크립트 작성
여기서부터가 Cowork의 진가가 발휘되는 부분입니다. 직접 코드를 짤 필요 없이 Cowork에 이렇게 시키시면 됩니다:
다음 텔레그램 채널들을 매일 자동으로 옵시디언 볼트에 마크다운으로 저장하는
파이썬 스크립트를 만들어줘.
채널: (추적할 채널 목록)
저장 경로: C:\Users\이름\Documents\ObsidianVault\Telegram
파일명 형식: YYYY-MM-DD_메시지ID.md
이미 저장된 메시지는 건너뛰기
api_id, api_hash는 .env 파일에서 읽어오기
Claude가 알아서 코드를 작성해줍니다. 결과물은 대략 이런 모양입니다:
from telethon import TelegramClient
from datetime import datetime
from pathlib import Path
import os
from dotenv import load_dotenv
load_dotenv()
API_ID = os.getenv('TELEGRAM_API_ID')
API_HASH = os.getenv('TELEGRAM_API_HASH')
VAULT_PATH = Path(r'C:\Users\사용자이름\Documents\ObsidianVault\Telegram')
CHANNELS = [] # 추적할 채널 목록
async def archive_channel(client, channel_name):
folder = VAULT_PATH / channel_name
folder.mkdir(parents=True, exist_ok=True)
async for msg in client.iter_messages(channel_name, limit=50):
if not msg.text:
continue
date = msg.date.strftime('%Y-%m-%d')
fname = folder / f"{date}_{msg.id}.md"
if fname.exists():
continue
content = f"---\nchannel: {channel_name}\ndate: {msg.date}\nid: {msg.id}\n---\n\n{msg.text}"
fname.write_text(content, encoding='utf-8')
async def main():
async with TelegramClient('session', API_ID, API_HASH) as client:
for ch in CHANNELS:
await archive_channel(client, ch)
import asyncio
asyncio.run(main())
.env 파일이란? 환경변수(설정값)를 저장하는 텍스트 파일입니다.
API 키 같은 비밀값을 코드에 직접 쓰지 않고 .env에 따로 적어두면, 깃허브에 코드를 올려도 비밀이 새지 않습니다.
4단계 — Cowork으로 매일 자동 실행
/schedule
매일 오전 9시에 위에서 만든 archiver.py 스크립트 실행해줘.
실행 후 새로 추가된 메시지 개수를 텔레그램으로 알려줘.
5단계 — 옵시디언 + Claude MCP 연결
옵시디언에 Claude MCP 플러그인을 깔면 Claude가 옵시디언 노트를 읽고 답변하는 게 가능해집니다.
"지난 일주일 동안 펀딩비 언급한 메시지 모두 찾아서 거래소별로 정리해줘"
실전 활용 사례 ─ 차익기회 패턴 데이터베이스
이걸 매번 직접 모니터링하면 늘 한 박자 늦습니다. 대신 스크래퍼가 채널들을 자동 아카이브 → 옵시디언에 메모리로 누적되어 있으면, 유사한 사건이 발생할 때 Claude에게 한 줄로 물어볼 수 있습니다:
옵시디언에서 "현현갭", "김프", "차익", "프리미엄" 키워드 들어간
지난 6개월 메시지 다 찾아줘. 어떤 토큰에서 몇 % 갭이 났는지,
얼마나 지속됐는지, 채널들이 언제 처음 언급했는지 표로 정리해.
Claude가 누적 메모리를 기반으로 즉시 분석해줍니다. "멍 때리고 있다가 기회 놓침"이 사라집니다. 이전 패턴이 옵시디언 안에 살아있고, 비슷한 사건이 또 발생하면 즉시 과거 패턴을 참조해서 전략을 만들 수 있습니다.


Triple Lock + 4종 Kill Switch ─ 실거래 안전망
실자금이 걸린 봇에서 가장 흔한 사고는 "복잡한 알고리즘 버그"가 아니라 "DRY_RUN 끄는 걸 잊은 채 배포"입니다.
이 챕터는 그런 종류의 사고를 코드 차원에서 막는 패턴을 다룹니다. 여기 나오는 5개 안전장치는 모두 30~50줄 짜리 stdlib-only 모듈로,
저장소의 10-foundation-modules/ 에 그대로 들어있습니다.
Triple Lock ─ 세 개의 환경변수가 모두 만족해야 실거래
ENABLED=true + DRY_RUN=false + LIVE_CONFIRM=true. 세 개가 독립적으로 모두 켜져야 실주문이 발사됩니다. 부팅 시점에 fail-fast로 검증합니다.
from triple_lock import require_live, is_live, status
# 부팅 시점 ─ fail-fast
require_live()
# RuntimeError: [triple_lock] 실거래 잠금 ─ DRY_RUN=true.
# 환경변수 확인: ENABLED=true, DRY_RUN=false, LIVE_CONFIRM=true
# 또는 분기로
live, reason = is_live()
if live:
await start_real_trading()
else:
logger.info(f"dry-run mode: {reason}")
await start_simulation()
.env에 DRY_RUN=False(대문자 F)로 적어두고 실거래 모드로 진입했다가 사고를 본 일이 있습니다.
이 모듈은 .lower() 처리하므로 False/FALSE/false 모두 동일하게 받지만,
다른 라이브러리는 그렇지 않습니다. 일관되게 소문자로 씁니다.
또한 LIVE_CONFIRM은 일부러 sudo-like 한 단계 더 둔 것입니다 ─ systemd unit에 박아두면 의미 없습니다.
사람이 매 배포마다 손으로 토글하라는 의도입니다.
AI가 자주 틀리는 부분
- "DRY_RUN이 없으면 dry-run으로 간주"가 아니라 "DRY_RUN!=false면 dry-run". 이 차이를 AI가 자주 거꾸로 짭니다 (default를 live로 둠 → 사고)
- truthy 판정을
bool(val)로 하면 빈 문자열 외에는 다 True가 됨 ─"false"문자열도 truthy..lower() in {"1","true","yes"}로 명시 매칭 필수 - 한 함수에 다 욱여넣지 않고
is_live()(non-throwing)와require_live()(throwing)를 분리해야 status 대시보드에서 재사용 가능
4종 Kill Switch
"5초 안에 폰으로 죽일 수 없으면 production이 아닙니다." 4가지 독립 메커니즘으로 봇을 차단합니다.
(1) Kill File ─ SSH 한 줄로 비상정지
빈 파일 하나 만들면 봇이 진입 안 합니다. signal/REST API/DB 다 안 됩니다 ─ 운영자 SSH 권한만 있으면 동작합니다.
# 운영자가 SSH 들어와서
$ touch /var/lib/mybot/data/KILL_ALL # 전체 차단
$ touch /var/lib/mybot/data/KILL_lighter # lighter만 차단
$ rm /var/lib/mybot/data/KILL_lighter # 해제
from kill_switch import KillSwitch
ks = KillSwitch(data_dir="/var/lib/mybot/data")
# 봇 메인 루프 ─ 매 신규 진입 직전
async def try_open_position(exchange, symbol):
ok, reason = ks.check(exchange)
if not ok:
logger.warning(f"[KILL] {exchange} 진입 차단: {reason}")
return
await exchange.create_order(...)
(2) 일일 PnL Stop (서킷브레이커)
일일 -X% 손실 시 자동 정지. 무한 손실을 막는 안전망입니다. 기본값은 자본의 5~10%. 잔고 스냅샷(거래소 API에서 직접 가져온 값)을 기준으로 계산합니다 ─ 로그의 PnL %는 부정확할 수 있습니다.
(3) 연속 실패 cap
연속 N회 주문 실패 시 자동 정지. AI 코드가 hallucinated API로 계속 401을 받는 상황 같은 패턴을 막습니다. 실패 카운터는 성공 시 리셋, 거래소별로 독립.
(4) 최소 자본 floor
잔고가 임계치 아래로 떨어지면 신규 진입 차단. 청산만 허용. 연속 손실로 자본이 녹아내릴 때 추가 진입을 막아 회복 여지를 남깁니다.
Trigger File 패턴 (Windows 친화)
Linux/Mac은 SIGHUP으로 봇에게 "config 다시 읽어"를 시키면 됩니다. Windows는 SIGHUP이 없습니다. 대안은 trigger file입니다. 봇이 N초마다 특정 파일의 mtime을 체크하고, 변하면 액션을 수행합니다.
trigger/
├── reload_config # touch 하면 config 재로드
├── pause # 존재하면 신규 진입 정지
├── close_all # 존재하면 전 포지션 청산 후 자기삭제
└── reload_strategy # touch 하면 전략 모듈 hot reload
Telegram bot이 명령을 받으면 trigger file을 touch만 하고 끝납니다. 실제 봇 프로세스는 자기 페이스로 trigger를 polling. 이 분리가 안전합니다 ─ Telegram bot이 죽어도 봇은 살고, 봇이 죽어도 다음 재기동 시 trigger를 처리합니다.
전체 layered defense
Foundation 모듈 11종 ─ 드롭인 빌딩 블록
저장소의 10-foundation-modules/ 폴더에는 봇 시스템에서 반복적으로 필요한 11개 모듈이 들어있습니다.
모두 stdlib only · 30~80줄 · MIT 라이선스. 자기 봇에 그대로 복사해서 쓸 수 있게 만들었습니다.
이 모듈들은 모두 Vibe Coding의 4-Mode 패턴으로 만들어졌고, README에 "AI에게 어떻게 시켰나"가 첫 프롬프트와 함께 정리되어 있습니다.
11개 모듈
- triple-lock-live ─ 실거래 진입 전 3-환경변수 fail-fast 잠금
- kill-switch ─ 파일 기반 비상정지 (전체/거래소별)
- circuit-breaker ─ 일일 PnL stop + 연속 실패 cap + 최소 자본 floor
- trigger-watcher ─ trigger file 폴링 + 핸들러 디스패치 (Windows 친화)
- telegram-notifier ─ throttled 알림 + 우선순위별 채널 분리
- state-persister ─
trader_state.json클린 복원, 재시작 시 orphan 방지 - health-monitor ─ 좀비 탐지(로그 mtime 기준 stale) + watchdog 재시작
- auto-scaler ─ 잔고/PnL에 따라 사이즈 자동 조정
- audit-log ─ JSON 구조화 로그 + DB 미러링
- env-loader ─
.env+ 환경변수 검증, 키 부재 시 fail-fast - subprocess-bridge ─ 나쁜 SDK(Lighter/GRVT/Reya)를 격리 프로세스로 분리
왜 이렇게 잘게 쪼개는가
한 모듈은 한 세션에서 AI가 잘 만들 수 있는 크기여야 합니다. 30~80줄, 단일 책임, stdlib만. 이 크기 안에서는 AI가 거의 실수하지 않고, 인간이 코드 리뷰로 5분 안에 검증할 수 있습니다. 500줄짜리 "통합 봇"은 AI가 만들면 어디서 깨졌는지 디버깅이 안 됩니다.
아키텍처 원칙
머리로 정한 것이 아니라 깨지면서 정착한 패턴들입니다.
- Async-first ─ 모든 거래소 래퍼가
asyncio기반. concurrent fetch / order가 디폴트 - Factory 패턴 ─
create_exchange("hyperliquid", **keys)가 유일한 진입점. 새 거래소 추가 시 caller 코드 변경 0 - 나쁜 SDK는 격리 프로세스 ─ Lighter, GRVT, Reya, Bulk 모두
__init__에서 sync HTTP로 메인 루프를 막아서, subprocess bridge로 분리 - 상태 영속화 ─
trader_state.json류 파일 매니저. 재시작 시 포지션/미체결/카운터를 클린 복원 - 텔레그램 = control plane ─
/status,/pnl,/balance,/positions,/restart,/reload,/close,/kill,/revive
각 모듈의 README가 같은 형식인 이유
저장소의 모든 foundation 모듈 README는 같은 6개 섹션으로 구성되어 있습니다. 이게 vibe coding의 결과물 표준입니다.
- 한 줄 요약 ─ 무슨 문제를 푸는 30줄짜리 코드인지
- 의존성 ─ Python 버전, 외부 라이브러리 (대부분 stdlib only)
- AI에게 어떻게 시켰나 ─ 첫 프롬프트 + AI가 자주 틀린 것
- 코드 (드롭인 단위) ─ 파일명 + 핵심 함수/클래스
- 사용 예시 ─ import해서 어떻게 부르는지
- 실전 함정 ─ battle-tested gotchas
이 형식이 중요한 이유는 다음에 같은 모듈을 다른 프로젝트에서 다시 만들 때 ─ 또는 AI에게 다시 시킬 때 ─ 첫 프롬프트와 함정을 그대로 재사용할 수 있기 때문입니다. 매번 처음부터 시작하지 않습니다.
Volume Farmer ─ 포인트 파밍 봇
무엇을 하는 봇인가요
요즘 새로 나오는 Perp DEX(탈중앙화 무기한 선물 거래소)들은 대부분 거래량 기반 포인트 시스템을 운영합니다. 거래량이 쌓이면 포인트가 적립되고, TGE(Token Generation Event, 토큰 발행 이벤트) 때 그 포인트에 비례해서 토큰을 에어드랍해주는 구조입니다.
수동으로 거래량을 채우다 보면 두 가지 비용이 발생합니다:
- 슬리피지(Slippage) ─ 시장가로 사고팔 때 호가 차이만큼 손실
- 방향 베팅 위험 ─ 가격이 반대로 가면 거래량은 채워도 잔고는 깎입니다
볼륨 파머는 이 두 가지를 동시에 해결합니다.
핵심 개념: 델타 중립 (Delta-Neutral)
델타(Delta)는 가격 변화에 대한 포지션의 노출도입니다. 롱(매수) 1개와 숏(매도) 1개를 동시에 들고 있으면 가격이 어디로 가든 손익이 0입니다. 이걸 "델타 중립"이라고 합니다.
가격이 오르든 내리든 보유 자산은 그대로 유지되면서 양쪽 거래소 포인트만 쌓입니다.
봇 골격 (의사코드)
async def farm_volume(target_volume_usd, exchange_a, exchange_b, symbol='ETH-PERP'):
"""
target_volume_usd: 목표 누적 거래량 (예: 100만 달러)
exchange_a, exchange_b: 두 거래소 연결 객체
"""
accumulated = 0
while accumulated < target_volume_usd:
# 1. 양쪽 거래소에서 동시에 시세를 가져옵니다
price_a, price_b = await asyncio.gather(
exchange_a.get_ticker(symbol),
exchange_b.get_ticker(symbol)
)
# 2. 두 거래소 가격 차이가 너무 크면 진입을 미룹니다
spread = abs(price_a - price_b) / price_a
if spread > MAX_SPREAD:
await asyncio.sleep(5)
continue
# 3. 동시에 양쪽 진입
size = calc_size_per_round(target_volume_usd)
await asyncio.gather(
exchange_a.market_buy(symbol, size),
exchange_b.market_sell(symbol, size)
)
# 4. 잠깐 보유했다가 동시에 청산
await asyncio.sleep(REST_INTERVAL)
await asyncio.gather(
exchange_a.market_sell(symbol, size),
exchange_b.market_buy(symbol, size)
)
accumulated += size * price_a * 2
이걸 처음부터 직접 짜실 필요는 없습니다. Cowork에 "Lighter와 Aster를 묶어서 ETH 델타 중립 볼륨 파밍 봇을 만들어줘. 안전장치는 ~~~ 넣어줘" 식으로 시키시면 됩니다.
반드시 박아둘 안전장치 4가지
1) Triple-lock (3중 잠금)
봇이 실거래에 들어가려면 3개의 스위치가 모두 켜져 있어야만 진입하게 만듭니다:
ENABLED=true─ 봇 활성화 여부DRY_RUN=false─ 모의실행 모드 해제 (true면 실거래 X)LIVE_CONFIRM=true─ 실거래 최종 확인
3개 중 하나라도 꺼져 있으면 시뮬레이션만 돌아갑니다. 실수로 새벽에 켜고 잠들어도 사고가 안 납니다.
2) Daily PnL Stop (일일 손실 한도)
if today_pnl < -MAX_DAILY_LOSS:
sys.exit("일일 손실 한도 초과, 봇 종료")
3) Min Collateral Floor (최소 증거금 보호)
증거금 비율이 위험 수준(예: 30%) 아래로 내려가면 자동으로 모든 포지션을 청산합니다. 청산(Liquidation) 당하기 전에 먼저 정리하는 셈입니다.
4) Kill File (즉시 정지 파일)
봇 폴더에 kill.txt라는 빈 파일이 있으면 즉시 종료하도록 설정합니다. 외출 중에 폰으로 봇을 끄고 싶을 때, 클라우드 동기화 폴더에 빈 파일 하나 만들기만 하면 됩니다.
실전 활용 사례 ─ 현물 갭 차익 자동화
이런 갭은 보통 신규 상장 직후나 한쪽 체인에서 큰 거래가 들어왔을 때 잠깐 발생합니다. 손으로 잡으려고 사이트 두 곳을 띄워놓고 새로고침하면 이미 갭이 닫혀버려요.
대신 자동화 봇을 운영하고 있다면:
- 각 거래소 가격을 1초마다 폴링(주기적 조회)
- 스프레드(가격 차이)가 임계치(예: 5%) 넘으면 즉시 알림
- 거래소별 유동성과 슬리피지 계산해서 실제 진입 가능 여부 판단
- 진입 가능하면 Triple-lock 검증 후 자동 진입
이런 흐름을 한 번 짜두면 잠자는 동안에도 갭이 발생하면 봇이 잡고 알려줍니다. 처음부터 봇이 자동 진입하는 게 부담스러우시면 알림만 받는 모드로 시작하시는 걸 추천합니다. "갭 5% 이상 나면 텔레그램 알림" 정도로 시작해서, 패턴이 익숙해진 다음 자동 진입 단계로 넘어가시면 안전해요.
페어 추천 (델타 중립 조합)
거래소 두 곳을 묶어야 하는데, 둘 다 포인트 시스템이 활발한 곳을 골라야 합니다.
- Lighter ↔ AsterDex ─ 둘 다 포인트 진행 중, 가스비(블록체인 수수료) 없거나 매우 낮음
- GRVT ↔ EdgeX ─ Maker rebate(메이커 주문 시 수수료 환급)가 좋아 비용 부담이 적음
- Pacifica ↔ Lighter ─ 솔라나 기반 신생 거래소와 검증된 거래소 조합
각 거래소 가입 + API 셋팅 방법은 GitHub 저장소의 20-exchange-wrappers/setup-guides/에 거래소별로 정리되어 있습니다.


알아두실 점
볼륨 파머는 실제 PnL 자체는 약간의 마이너스로 끝나는 게 보통입니다. 수수료, 펀딩비, 슬리피지 누적 비용이 있으니까요. 이걸 감수하는 이유는 "포인트의 미래 가치"가 그 비용보다 클 거라는 베팅이기 때문입니다.
적용 기준:
- TGE가 가까운 프로젝트인가
- 포인트 분배 구조가 합리적인가 (특정 고래에게 다 가지 않는가)
- 감당 가능한 비용 한도 내인가
멀티 Perp DEX 페어 트레이딩 봇
왜 페어 트레이딩인가요
ETH와 SOL 같은 두 자산은 시장 전체와 같이 움직이는 경향이 강합니다. 한 자산만 베팅하면 시장 방향에 노출되지만, 한쪽을 롱 / 다른 쪽을 숏 하면 "두 자산의 상대적 움직임"에만 베팅하게 됩니다. 시장이 폭락해도 영향을 덜 받습니다.
이걸 가능하게 하는 통계 도구가 코인테그레이션(Cointegration)입니다.
핵심 수식 ─ Kalman Filter Hedge Ratio
ETH와 SOL의 적정 비율(Hedge Ratio)은 시장 상황에 따라 계속 변합니다. 고정값으로 박아두면 며칠 못 가서 스프레드가 발산합니다. Kalman Filter로 매 틱마다 비율을 동적으로 갱신해야 합니다.
import numpy as np
class KalmanHedgeRatio:
"""
state: [hedge_ratio, intercept]
measurement: ETH_price = hedge_ratio * SOL_price + intercept + noise
"""
def __init__(self, delta=1e-4, R=0.001):
self.beta = np.array([1.0, 0.0]) # 초기 hedge_ratio=1, intercept=0
self.P = np.eye(2) * 1.0 # 초기 공분산
self.delta = delta # 프로세스 노이즈 (얼마나 빨리 변하게 할지)
self.R = R # 관측 노이즈
def update(self, sol_price, eth_price):
# 예측 단계
F = np.array([sol_price, 1.0])
y_pred = F @ self.beta
residual = eth_price - y_pred
# 칼만 게인
S = F @ self.P @ F + self.R
K = self.P @ F / S
# 상태 갱신
self.beta = self.beta + K * residual
self.P = self.P - np.outer(K, F) @ self.P + np.eye(2) * self.delta
return self.beta[0], self.beta[1], residual # ratio, intercept, z-input
# 사용
kf = KalmanHedgeRatio()
ratio, intercept, residual = kf.update(sol_price, eth_price)
Z-Score 시그널 생성
Hedge ratio가 갱신될 때마다 스프레드(Spread)를 계산합니다:
spread = ETH - hedge_ratio × SOL - intercept.
이 스프레드의 최근 N틱 평균과 표준편차를 계산해서 Z-Score를 구합니다.
from collections import deque
class ZScoreSignal:
def __init__(self, lookback=200, entry_z=2.0, exit_z=0.5):
self.spreads = deque(maxlen=lookback)
self.entry_z = entry_z
self.exit_z = exit_z
def update(self, spread):
self.spreads.append(spread)
if len(self.spreads) < 50:
return None # 데이터 부족
arr = np.array(self.spreads)
z = (spread - arr.mean()) / (arr.std() + 1e-9)
# 시그널 발생
if z > self.entry_z:
return 'short_eth_long_sol' # 스프레드 너무 큼 → 좁혀질 거 베팅
elif z < -self.entry_z:
return 'long_eth_short_sol'
elif abs(z) < self.exit_z:
return 'close' # 평균 회귀 완료
return None
Ornstein-Uhlenbeck 프로세스 기반 트레일링 스탑
진입은 위 Z-Score로 하지만, 청산 타이밍이 진짜 알파입니다. 스프레드가 평균회귀하는 속도를 OU 프로세스로 모델링하면 "지금 닫지 않으면 다시 벌어질 가능성이 X%" 같은 정량적 판단이 가능합니다.
dX = θ(μ - X)dt + σdW 형태의 미분방정식이며, θ가 "회귀 속도", μ가 "평균", σ가 "변동성"입니다.
스프레드가 OU 프로세스면 평균회귀 속도와 노이즈를 따로 추정할 수 있습니다.
운영 봇의 핵심 파라미터:
# trailing_stop_manager.py
class OUTrailingStop:
"""
OU 프레임워크 기반 동적 트레일링 스탑
"""
def __init__(self,
trail_gap_sigma=0.30, # 최고 PnL 대비 σ 만큼 떨어지면 청산
max_hold_seconds=1800, # 30분 강제 청산
ou_lookback=500):
self.trail_gap_sigma = trail_gap_sigma
self.max_hold_seconds = max_hold_seconds
self.ou_lookback = ou_lookback
def estimate_ou_params(self, spread_history):
"""
Maximum Likelihood로 (theta, mu, sigma) 추정
"""
x = np.array(spread_history)
dx = np.diff(x)
x_t = x[:-1]
# Linear regression: dx = theta*(mu - x_t)*dt + noise
slope, intercept = np.polyfit(x_t, dx, 1)
theta = -slope
mu = intercept / theta if theta > 0 else x.mean()
residuals = dx - (theta * (mu - x_t))
sigma = residuals.std()
return theta, mu, sigma
def should_exit(self, position, current_pnl, peak_pnl, spread_now, spread_history, elapsed_sec):
# 1. 시간 강제 청산
if elapsed_sec > self.max_hold_seconds:
return True, "max_hold_exceeded"
# 2. OU 파라미터 추정
theta, mu, sigma = self.estimate_ou_params(spread_history[-self.ou_lookback:])
# 3. 트레일링 스탑: peak에서 trail_gap_sigma×σ 만큼 빠지면 청산
if (peak_pnl - current_pnl) > self.trail_gap_sigma * sigma * position.size:
return True, "trailing_stop_hit"
# 4. 스프레드가 mu를 넘어 반대편으로 갔으면 청산 (over-shoot)
if position.side == 'long_spread' and spread_now > mu:
return True, "spread_crossed_mean"
if position.side == 'short_spread' and spread_now < mu:
return True, "spread_crossed_mean"
return False, None
trail_gap_sigma=0.30, max_hold_seconds=1800은 ETH/SOL 페어 기준 여러 시도 끝에 안착시킨 값입니다. 다른 페어(예: BTC/ETH)는 변동성이 다르므로
각자 백테스트로 운영 페어에 맞는 값을 찾으셔야 합니다.
5거래소 동시 운영 구조
거래량이 분산되어 있어서 한 거래소만 쓰면 진입/청산 시 슬리피지가 큽니다. 대신 5개 거래소에 분산하면 같은 사이즈도 슬리피지 없이 빠르게 진입할 수 있습니다. 또한 거래소별 펀딩비 차이로 cherry-picking도 가능해요.
HyperliquidWrapper 등)는 §14 GitHub 레포의 exchange_wrappers/ 모듈에 정의되어 있습니다.EXCHANGES = {
'hyperliquid': HyperliquidWrapper(...),
'nado': NadoWrapper(...),
'standx': StandXWrapper(...),
'treadfi': TreadFiWrapper(...),
}
async def execute_signal(signal, symbol, total_size):
"""
신호 발생 시 4개 거래소에 사이즈 분산
펀딩비 유리한 거래소에 더 많이 배분
"""
# 펀딩비 조회
funding = await asyncio.gather(*[ex.get_funding(symbol) for ex in EXCHANGES.values()])
# 롱 포지션이면 펀딩비 낮은(or 마이너스) 거래소 우선
# 숏 포지션이면 펀딩비 높은 거래소 우선
side = 'long' if signal == 'long_eth_short_sol' else 'short'
weights = compute_weights(funding, side) # softmax-like 가중치
sizes = {name: total_size * w for name, w in weights.items()}
# 동시 진입
tasks = []
for name, size in sizes.items():
if size > MIN_SIZE:
tasks.append(EXCHANGES[name].place_order(symbol, side, size))
results = await asyncio.gather(*tasks, return_exceptions=True)
return results
실전 운영 노하우
- Cointegration test 매일 재검증. ETH/SOL이 항상 코인테그레이트되지는 않습니다. ADF 테스트(Augmented Dickey-Fuller)로 매일 재검증하고, p-value > 0.05면 봇 정지
- 스프레드가 발산할 때 추가 진입 금지. Z=3을 넘어가면 "이것이 평균회귀인지, 새로운 균형인가" 판단 안 됨. 이때는 무조건 손절 후 대기
- 거래소별 latency 차이 보정. 5개 거래소 동시 진입을 시도해도 응답 속도 차이로 한쪽만 체결되는 경우가 있어, 체결 모니터링 후 미체결 leg는 즉시 시장가로 보충
- 가스비 + 메이커 리베이트 회계. 5거래소 운영하면 가스비가 누적됨. PnL 계산 시 모든 비용 포함해야 진짜 알파인지 판단 가능

김프 · Cross-Venue 차익거래
김프 차익의 구조
김프는 한국에서 외환 송금 규제와 원화 결제 한정성 때문에 만성적으로 발생합니다. 보통 2~5% 사이를 오가다가 시장이 과열되면 10%까지 벌어지기도 해요. 이 갭을 잡으려면 한국 거래소에서 매도 + 글로벌 거래소에서 매수를 동시에 해야 합니다.
Cross-Venue 스캐너 ─ 신규 상장 캐치
신규 토큰이 거래소 A에 먼저 상장되고 30분~몇 시간 뒤 거래소 B에 상장되는 경우가 많습니다. 이 시차 동안 가격이 크게 벌어지는데, 봇이 자동 감지하면 분 단위로 잡을 수 있습니다.
exchanges 인자로 전달되는 거래소 객체는 §14 GitHub 참조.import asyncio
from typing import Set
class NewListingScanner:
"""
여러 거래소의 신규 상장 페어를 1분마다 폴링,
같은 토큰이 여러 곳에 상장되는 시점에 가격 갭 계산
"""
def __init__(self, exchanges):
self.exchanges = exchanges
self.known_symbols = {ex.name: set() for ex in exchanges}
async def scan_once(self):
"""각 거래소의 현재 listing 가져와서 신규 발견"""
new_listings = {}
for ex in self.exchanges:
current = await ex.fetch_all_symbols() # set of symbols
new = current - self.known_symbols[ex.name]
if new:
new_listings[ex.name] = new
self.known_symbols[ex.name] = current
return new_listings
async def find_arb_opportunity(self, symbol):
"""같은 심볼이 여러 거래소에 있을 때 가격 비교"""
prices = {}
for ex in self.exchanges:
try:
p = await ex.get_ticker(symbol)
if p and p > 0:
prices[ex.name] = p
except Exception:
continue
if len(prices) < 2:
return None
max_ex = max(prices, key=prices.get)
min_ex = min(prices, key=prices.get)
gap_pct = (prices[max_ex] - prices[min_ex]) / prices[min_ex] * 100
if gap_pct > MIN_ARB_PCT: # 예: 3%
return {
'symbol': symbol,
'sell_at': max_ex,
'buy_at': min_ex,
'gap_pct': gap_pct,
'prices': prices
}
return None
async def run(self):
while True:
try:
new_listings = await self.scan_once()
for ex_name, symbols in new_listings.items():
for sym in symbols:
# 다른 거래소에도 있으면 갭 체크
opp = await self.find_arb_opportunity(sym)
if opp:
await self.alert_telegram(opp)
if AUTO_EXECUTE:
await self.execute_arb(opp)
except Exception as e:
logger.error(f"scan error: {e}")
await asyncio.sleep(POLL_INTERVAL) # 60초
실전 진입 시 주의사항
- 유동성 확인 필수. 갭이 5%여도 한쪽 거래소에 유동성 없으면 진입 자체가 불가능. 호가창 깊이(orderbook depth)를 매수/매도 양쪽 다 확인
- 출금 가능 여부. 한쪽에서 사고 다른 쪽으로 옮기는 전략이면, 두 거래소가 모두 입출금 열려있는지 사전 확인. 신규 상장은 입출금이 닫혀있는 경우 많음
- 네트워크 + 컨펌 시간. 입출금 컨펌이 30분 걸리면 그 동안 갭이 닫힘. 한쪽이 빠른 체인(Solana, Tron)이면 유리
- 슬리피지 모델링. 시장가로 진입하면 호가창 한 호가만 먹는 게 아니라 여러 호가를 먹어서 평균 진입가가 표시 가격과 차이남. 호가창 깊이를 봇이 미리 계산해야 함
Aster 현물 매수 봇 + Hedge Leg 조합
한 가지 운영 사례입니다. Aster의 신규 현물 상장이 발표되면 즉시 매수하고, 동시에 다른 거래소에서 같은 토큰의 perp을 숏 헤지합니다.
async def aster_spot_listing_strategy(token_symbol, target_size_usd):
"""
Aster 신규 현물 상장 → 즉시 매수 + perp 헤지
"""
# 1. Aster 현물에 시장가 매수
spot_buy = await aster.spot_market_buy(token_symbol, target_size_usd)
actual_qty = spot_buy['filled_qty']
spot_price = spot_buy['avg_price']
# 2. 다른 거래소에서 같은 토큰 perp 찾기
perp_venues = await find_perp_listings(token_symbol)
if not perp_venues:
# perp 없으면 BTC 숏으로 시장 베타만 헤지
await binance.perp_market_sell('BTC-PERP', target_size_usd * 0.4)
return
# 3. 가장 깊은 거래소에서 perp 숏
best_perp = max(perp_venues, key=lambda x: x['depth'])
await best_perp['ex'].perp_market_sell(token_symbol, actual_qty)
# 4. Aster 현물에 트레일링 매도 주문 (강한 상승 시 익절)
await aster.create_trailing_stop_sell(
token_symbol,
qty=actual_qty,
trigger_callback_pct=8.0 # 고점 대비 8% 빠지면 청산
)
봇을 24시간 돌릴 때 알아야 할 인프라 원칙
의존성 충돌과 격리된 환경
여러 거래소를 동시에 쓰다 보면 각 거래소 SDK(Software Development Kit)가 서로 충돌하는 일이 생깁니다. A 거래소는 라이브러리 X의 1.0 버전을, B 거래소는 같은 라이브러리의 2.0 버전을 요구할 수 있거든요.
해결 방법은 venv(Virtual Environment, 가상환경)를 모듈마다 따로 만드는 겁니다.
# A 거래소용 가상환경
python -m venv venv_exchange_a
venv_exchange_a\Scripts\activate
pip install exchange_a_sdk
# B 거래소용 가상환경 (별도)
python -m venv venv_exchange_b
venv_exchange_b\Scripts\activate
pip install exchange_b_sdk
그 다음 메인 봇은 각 거래소를 호출할 때 subprocess(별도 프로세스)로 격리해서 실행합니다.
VENV_PATHS 상수와 각 거래소별 runner 모듈은 §14 GitHub 참조.# shared-utils/subprocess_wrapper.py
async def call_exchange_isolated(exchange, method, *args):
venv_python = VENV_PATHS[exchange]
proc = await asyncio.create_subprocess_exec(
venv_python, '-c',
f'from {exchange}_runner import {method}; print({method}({args}))',
stdout=asyncio.subprocess.PIPE
)
out, _ = await proc.communicate()
return json.loads(out)
이렇게 하면 한 거래소에서 문제가 생겨도 메인 봇이 멈추지 않습니다.
데이터 저장 ─ SQLite WAL 모드
봇이 매일 펀딩비, 청산 데이터, 거래 기록을 저장해야 하는데, 일반적인 데이터 저장 방식으로는 동시에 여러 봇이 같은 파일에 쓰려고 하면 충돌이 납니다.
SQLite WAL(Write-Ahead Logging) 모드가 답입니다. 별도 서버 띄울 필요 없이 파일 하나로 끝나서 트레이더가 직접 운영하기 편해요.
import sqlite3
conn = sqlite3.connect('bot_data.db')
conn.execute('PRAGMA journal_mode=WAL') # 이 한 줄
초당 10,000+ 쓰기가 필요한 경우(펀딩 스냅샷, 청산 로그)는 Rust로 짜고 PyO3로 바인딩하면
초당 169,000 rows까지 가능합니다. 협업자 레포의 rust-services/gap-recorder가 이 패턴입니다.
텔레그램 원격 제어 봇
24시간 봇이 돌아도 사람이 매일 컴퓨터 앞에 있을 수는 없습니다.
/status - 모든 봇이 살아있는지 확인
/pnl - 오늘 손익
/balance - 거래소별 잔고
/positions - 현재 들고 있는 포지션
/restart - 특정 봇 재시작
/kill - 즉시 정지
/revive - 재가동
BotFather에서 새 봇 만들고 토큰 받아서 Cowork에 시키면 됩니다.

상태 저장 (State Persistence)
state = {
'positions': {'ETH-PERP': {'side': 'long', 'size': 1.5, 'entry': 3200}},
'pnl_today': 12.5,
'last_update': '2026-01-15 14:23:00'
}
with open('trader_state.json', 'w') as f:
json.dump(state, f, indent=2)
봇이 재시작되면 이 파일을 읽어서 이어서 돌립니다. 메모리에만 의존하면 절대 안 됩니다.
공개 코드 ─ Cowork Trading Stack
이 가이드의 모든 코드와 운영 노트는 GitHub에 공개되어 있습니다. 콘텐츠는 CC BY-NC 4.0, 코드는 MIT 라이선스로 풀어두었습니다.
→ github.com/coinmage777/cowork-trading-stack
저장소 구조
00-vibe-coding-basics/ AI 코딩 입문 5장
10-foundation-modules/ 드롭인 빌딩 블록 11종 (kill-switch, notifier, auto-scaler 등)
20-exchange-wrappers/ 22개 거래소 통합 + setup-guides
30-strategy-patterns/ 전략 템플릿 (pair, MM, DCA, volume-farmer, aster-spot-buyer, backtest)
40-realtime-infra/ 실시간 스캐너 (cross-venue arb, kimp listing, spot-spot, wallet trackers)
50-rust-acceleration/ PyO3 핫패스 (advanced)
60-ops-runbooks/ tmux / systemd / telegram-control 운영 5종
80-obsidian-vault/ Obsidian 활용법 (구조 / 템플릿 / Dataview / AI 연동)
99-glossary/ 용어 정리
ko/ 장문 한국어 가이드 (8 챕터)
누구에게 어느 폴더가 맞는가
- AI로 새 시스템을 짜는 빌더 ─
10-foundation-modules/+30-strategy-patterns/부터 - 이미 돌고 있는 시스템을 안정 운영하려는 분 ─
60-ops-runbooks/+40-realtime-infra/ - 성능을 더 짜내고 싶은 고급 사용자 ─
50-rust-acceleration/+20-exchange-wrappers/_combined/의 격리 프로세스 패턴 - vibe coding이 처음인 분 ─
00-vibe-coding-basics/5개 문서부터
5분 시작 (Quick Start)
가장 가벼운 모듈은 5분 안에 띄워볼 수 있습니다.
git clone https://github.com/coinmage777/cowork-trading-stack.git
cd cowork-trading-stack/30-strategy-patterns/volume-farmer/
python -m venv venv && source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # API 키 채우고 DRY_RUN=true 로 시작
모듈마다 자체 README와 의존성을 가집니다. 최상위에 "한 번에 다 돌리는 명령"은 없습니다 ─ 안전하지도, 가능하지도 않기 때문입니다.
추천 진입점 3개
00-vibe-coding-basics/01-claude-code-setup.md─ Claude Code 셋업 (10분)00-vibe-coding-basics/02-prompting-patterns.md─ 실전 프롬프트 패턴30-strategy-patterns/_combined/README.md─ 전략 모듈을 조합하는 방식
자주 등장하는 용어 정리
| 용어 | 짧은 설명 |
|---|---|
| Perp DEX | 탈중앙화 무기한 선물 거래소. 만기일이 없는 선물을 블록체인 위에서 거래 |
| 펀딩비 (Funding Rate) | Perp에서 롱과 숏 균형 맞추려 8시간마다 정산하는 수수료 |
| 델타 중립 (Delta-Neutral) | 가격 방향 노출이 0인 포지션 (롱+숏 동일 사이즈) |
| 슬리피지 (Slippage) | 시장가 거래 시 호가 차이로 인한 손실 |
| 코인테그레이션 (Cointegration) | 두 시계열이 같이 움직이는 통계적 성질. 페어 트레이딩의 기반 |
| Kalman Filter | 매 틱마다 hedge ratio 같은 동적 변수를 갱신하는 통계 기법 |
| OU Process | Ornstein-Uhlenbeck. 평균회귀 속도를 모델링하는 확률과정 |
| Z-Score | 평균 대비 표준편차 몇 개만큼 떨어졌는지 측정. 진입 임계치로 사용 |
| ADF Test | Augmented Dickey-Fuller. 시계열의 정상성(stationarity) 검정 |
| Sharpe Ratio | (수익률 - 무위험이자율) / 표준편차. 위험조정 수익률 지표 |
| MDD | Maximum Drawdown. 최대 낙폭 (전략의 최악 손실 구간) |
| Kelly Criterion | 이론적 최적 베팅 사이즈. 실전은 1/4 (Fractional) Kelly 권장 |
| RSI | Relative Strength Index. 14틱 기준 과매수/과매도 지표 |
| Bollinger Bands | 이동평균 ± 2σ로 상하단 밴드 그리는 변동성 지표 |
| VWAP | Volume-Weighted Average Price. 거래량 가중 평균가 |
| 베이지안 prior | 과거 결과로 미래 확률을 갱신하는 통계 기법 |
| TGE | Token Generation Event, 토큰 발행 이벤트 |
| 에어드랍 | 프로젝트가 사용자에게 토큰을 무료 분배 |
| 포인트 시스템 | 거래량/활동 기반 포인트 적립, TGE 때 토큰으로 전환되는 구조 |
| Maker / Taker | 호가 거는 사람(Maker) / 즉시 잡아가는 사람(Taker) |
| Builder Code | 거래소가 특정 프론트엔드 거래에 수수료 일부 환급해주는 시스템 |
| CEX / DEX | 중앙화 거래소 / 탈중앙화 거래소 |
| 김프 | 한국 프리미엄. 한국 거래소가 해외보다 비싼 정도 |
| Cross-venue | 여러 거래소(venue) 사이를 가로지르는 거래 |
| Layer 2 | 메인 블록체인 위에 올린 보조 체인 (수수료/속도 개선) |
| 가스비 | 블록체인 트랜잭션 수수료 |
| Offchain | 블록체인 밖에서 처리하고 결과만 체인에 기록 |
| PnL | Profit and Loss, 손익 |
| DCA | Dollar Cost Averaging, 분할매수 |
| MCP | Model Context Protocol. AI가 외부 데이터/도구에 접근하는 표준 |
| 마크다운 | ## 제목 같은 기호로 서식 표시하는 텍스트 형식 |
| venv | Virtual Environment. 격리된 파이썬 작업 공간 |
| subprocess | 별도 프로세스로 코드 실행 (충돌 방지) |
| SDK | Software Development Kit. 거래소가 제공하는 코드 라이브러리 |
| API 키 | 코드로 거래소에 접근할 때 쓰는 인증 키 (절대 공개 X) |
| DRY_RUN | 모의실행. 실거래 없이 시뮬레이션만 |
| 레버리지 | 자본의 N배로 거래하는 것 (수익도 손실도 N배) |
| 청산 (Liquidation) | 증거금 부족 시 거래소가 강제로 포지션 정리 |
| OHLCV | 시가/고가/저가/종가/거래량 (캔들 데이터의 기본 단위) |
| 백테스팅 | 과거 데이터에 전략을 적용해 결과 시뮬레이션 |
| Adversarial Workflow | 여러 AI에게 같은 코드 검수시켜 결함을 교차 검증하는 방식 |
| Vibe Coding | AI에게 의도를 전달하고 결과를 검증하는 협업 프로토콜. 코드 생성기가 아니라 협업 파트너로 다루는 방식 |
| Gaslight My AI | "라이벌 AI가 검토할 거다" 식 framing으로 같은 모델의 리뷰 품질을 끌어올리는 패턴 |
| CLAUDE.md | 프로젝트 루트에 두면 Claude Code가 자동으로 읽는 컨텍스트 파일. 불변 규칙 · 자주 쓰는 명령 · 위험 영역 경고를 적음 |
| Auto-memory | 세션을 넘어 지속되는 사용자 메모리. user/feedback/project/reference 4 타입으로 분류 |
| Triple Lock | ENABLED + DRY_RUN=false + LIVE_CONFIRM 3개 환경변수가 모두 만족해야 실거래 진입을 허용하는 fail-fast 잠금 |
| Kill Switch | 봇 진입을 즉시 차단/해제하는 메커니즘. 파일 기반 · PnL 기반 · 실패 카운터 · 자본 floor 4종 |
| Trigger File | Windows에서 SIGHUP 대체로 쓰는 파일 mtime 기반 신호 패턴 (config 재로드, 일시정지, 청산 등) |