5년치 캔들 데이터로 정책을 만들고, 차트 이미지를 보는 모델로 검증하기
왜 VLM인가
트레이딩 데이터는 숫자이지만, 사람이 시장을 볼 때는 차트를 본다. 캔들, 이동평균, 거래량, MACD, RSI가 한 화면에 놓이면 숫자 표보다 훨씬 빠르게 구조를 파악할 수 있다. 이 장의 아이디어는 바로 그 지점에서 출발한다. 정책은 숫자로 만들고, 모델은 차트 이미지로 그 정책을 배우게 한다.
red 프로젝트는 이 구조를 실험한다. DB에서 캔들 데이터를 가져오고, 이상 구간 라벨을 만들고, 정책 문서의 원칙을 teacher 로짓으로 근사한 뒤, 최종적으로 차트 PNG를 입력받아 매도, 매수, 유지 중 하나를 출력하는 정책 모델을 학습한다.
아래 1단계부터 6단계는 앞서 만든 red 실험이다. 라벨과 teacher 정책으로 정답을 만든 뒤, 작은 시각 모델이 그 정답을 따라 하게 했다. 지금 이더 프로그램의 본체는 green이다. 차트 이미지를 비전 모델에게 직접 보여주고, 모의 포트폴리오에서 그 의견과 이동평균 규칙을 나란히 확인한다.
전체 파이프라인
5년치 ETH/KRW 캔들 DB
-> 지표 계산: MA20, MA60, MA120, MACD, RSI, 거래량
-> 극단 라벨: crash, surge, price_spike, volume_spike, pattern_anomaly
-> 정책 teacher: policy.md 규칙을 매도/매수/유지 로짓으로 변환
-> ACTION CSV: 0=매도, 1=매수, 2=유지
-> 차트 PNG 생성
-> CNN 기반 PNG 시각 정책망 학습
-> 모의 포트폴리오 검증프로젝트 구성
| 파일 | 역할 |
|---|---|
| Market.py | MySQL 캔들 데이터를 읽고 보조지표와 이상탐지 기능을 제공한다. |
| config/policy.md | 거래 비용, 이동평균, 수렴/발산, 급등락, MACD 기준을 자연어 정책으로 적는다. |
| build_market_extreme_labels.py | 과거 캔들 전체에 crash, surge, volume_spike 같은 극단 라벨을 붙인다. |
| build_action_from_labels.py | 극단 라벨과 teacher 정책을 매도/매수/유지 ACTION CSV로 바꾼다. |
| lib/rl_policy_teacher.py | policy.md를 수치 규칙으로 근사해 teacher logits를 만든다. |
| train_png_policy_from_action_csv.py | 차트 PNG와 ACTION CSV를 이용해 PNG->액션 정책을 교차엔트로피로 학습한다. |
| PngToStatus.py | 차트 이미지를 CNN으로 읽고 포트폴리오 상태 특징과 합쳐 3개 액션 로짓을 낸다. |
| lib/png_status_vision_viewer.py | 학습된 PNG 정책을 뷰어에 연결하고 액션 CSV와의 일치율, 모의 수익률을 확인한다. |
1단계: 5년치 데이터 준비
암호화폐는 24시간 거래되므로 데이터의 양이 빠르게 커진다. 1분봉 기준 5년이면 약 260만 개 이상의 봉이 생길 수 있다. 이 프로젝트는 Market.get_data()를 통해 DB에서 과거 캔들을 읽고, open, high, low, close, volume을 기본으로 사용한다. 그 위에 MA20, MA60, MA120, MACD, RSI 같은 보조지표를 붙인다.
프로젝트에 현재 생성된 캐시 기준으로 market_extreme_labels.csv는 788,824개 봉을 포함한다. 현재 캐시는 5년치 전체 데이터가 아니라 구축 방법을 보여 주는 예시다. 5년 전체 데이터를 사용할 때는 같은 과정을 더 긴 기간에 반복한다.
| 데이터 요소 | 이유 |
|---|---|
| OHLCV | 가격의 움직임과 거래량을 나타내는 기본 재료다. |
| MA20/60/120 | 단기, 중기, 장기 추세와 수렴/발산을 판단한다. |
| MACD | 추세의 방향과 모멘텀 잔량을 본다. |
| RSI | 과열과 침체 정도를 보조적으로 확인한다. |
| 이상 라벨 | 급락, 급등, 가격 급변, 거래량 급증, 패턴 이상을 정책에 반영한다. |
2단계: 극단 구간 라벨 만들기
build_market_extreme_labels.py는 각 봉을 돌면서 AdvancedAnomalyDetector를 실행한다. 최근 50개 봉을 기준으로 가격 급변, 거래량 급증, IsolationForest 패턴 이상, 지지선 이탈, 저항선 돌파 같은 신호를 계산한다. 결과는 ext_crash_signal, ext_surge_signal, ext_price_spike, ext_volume_spike, ext_pattern_anomaly 같은 컬럼으로 저장된다.
python build_market_extreme_labels.py
# 출력
# cache/market_extreme_labels.csv
# rows=788,824| 현재 라벨 분포 | 개수 |
|---|---|
| normal | 701,266 |
| volume_spike | 51,744 |
| price_spike | 20,110 |
| pattern_anomaly | 15,597 |
| surge | 54 |
| crash | 51 |
| crash_surge_conflict | 2 |
3단계: 정책 문서를 teacher로 바꾸기
config/policy.md에는 사람이 이해하는 매매 원칙이 적혀 있다. 예를 들어 왕복 수수료를 고려해 1% 미만의 얕은 익절을 피하고, 장대 양봉은 매수 쪽으로, 장대 음봉은 매도 쪽으로 본다. MA20, MA60, MA120의 수렴과 발산, MA60의 방향, 고점 수렴 리스크, MACD 모멘텀도 함께 본다.
이 자연어 정책은 그대로 학습에 넣을 수 없으므로 rl_policy_teacher.py가 수치 규칙으로 근사한다. 각 봉에서 매도, 매수, 유지 세 액션에 점수를 주고, 가장 높은 점수를 teacher 행동으로 삼는다. 이 방식은 LLM이 무작정 결정하게 하는 것보다 재현성이 좋고, 왜 그런 액션이 나왔는지 설명하기 쉽다.
| 정책 원칙 | teacher 로짓에서의 근사 |
|---|---|
| 장대 양봉 | 매수 점수를 크게 올리고 유지 점수를 낮춘다. |
| 장대 음봉 | 매도 점수를 크게 올리고 무포지션 매수를 억제한다. |
| 수수료 1% 미만 | 보유 중 얕은 이익에서는 성급한 매도를 억제한다. |
| MA60 하향 | 매수 점수를 낮추고 매도와 유지를 높인다. |
| 고점 부근 MA 수렴 | 신규 매수를 낮추고 관망 또는 매도 쪽을 높인다. |
| crash/surge 라벨 | crash는 매도, surge는 매수 쪽 점수를 보강한다. |
4단계: ACTION CSV 만들기
build_action_from_labels.py는 라벨 CSV를 읽고 ACTION CSV를 만든다. 액션은 0=매도, 1=매수, 2=유지다. regime 모드는 crash와 surge 같은 레짐만 보고 단순 매핑하고, teacher 모드는 policy.md를 근사한 teacher logits의 argmax를 사용한다. --walk 옵션을 켜면 모의 포트폴리오 상태를 봉마다 갱신하면서 정책을 만든다.
python build_action_from_labels.py \
-o cache/ACTION_from_labels_teacher.CSV \
--mode teacher \
--walk
python build_action_from_labels.py \
-o cache/ACTION_repaired.CSV \
--mode teacher \
--walk \
--repair-pnl skip-buy현재 보정된 ACTION_repaired.CSV는 788,824행이며, 매도 148개, 매수 489개, 유지 788,187개로 구성된다. 유지 비중은 약 99.919%다. 이처럼 클래스가 불균형하면 항상 유지만 예측해도 전체 일치율이 매우 높을 수 있으므로, 높은 일치율이나 낮은 학습 손실만으로 매매 판단 성능을 주장할 수 없다.
5단계: CNN 기반 시각 정책망 학습하기
train_png_policy_from_action_csv.py는 ACTION CSV를 teacher로 삼아 차트 PNG를 보는 정책을 학습한다. red 프로젝트는 언어 처리를 하지 않는다. 차트 이미지를 읽는 작은 CNN과 포트폴리오 숫자 특징을 결합해 매도, 매수, 유지 중 하나를 고르는 CNN 기반 시각 정책망이다.
입력은 차트 PNG와 사이드 특징이다. 사이드 특징은 현재 포지션 보유 여부, 미실현 손익, 현금 비율 3개다. 차트만 보면 같은 가격 구조라도 현재 보유 중인지 아닌지를 알 수 없기 때문에 이 보조 정보가 필요하다.
| 입력 | 설명 |
|---|---|
| 차트 PNG | 캔들, 이동평균, 거래량, 보조지표가 그려진 화면 이미지다. |
| in_position | 현재 코인을 보유 중인지 여부다. |
| unrealized_pnl | 보유 중일 때 매수가 대비 미실현 손익이다. |
| cash_ratio | 전체 평가액 중 현금 비율이다. |
| target action | ACTION CSV의 0, 1, 2 라벨이다. |
python train_png_policy_from_action_csv.py \
--actions cache/ACTION_repaired.CSV \
--out cache/png_status_policy_action_repaired.pt \
--epochs 8 \
--stride 3 \
--max-samples 12000| 학습 기록 | 값 |
|---|---|
| 샘플 수 | 12,000 |
| 입력 해상도 | 96 x 192 |
| hidden dimension | 256 |
| epoch | 8 |
| CE loss | 1 epoch 0.01693 -> 8 epoch 0.00075 |
| 체크포인트 | cache/png_status_policy_action_repaired.pt |
6단계: 실제 검증
검증에서는 전체 일치율과 손실 외에 행동별 precision·recall·F1과 혼동행렬을 확인한다. 모의 포트폴리오에서는 실제 거래 횟수, 거래 비용 반영 수익률과 최대 낙폭을 기록하고, 학습 구간보다 뒤의 시간 분리 테스트에서 재검증한다. 차트 브라우저를 이용한 사람 검토는 이 정량 평가를 보완한다.
| 검증 항목 | 확인할 것 |
|---|---|
| 액션 일치율 | 정책 모델의 argmax가 ACTION_repaired.CSV의 라벨과 일치하는 비율 |
| 체결 일치율 | 실제 포트폴리오 상태에서 실행된 행동이 teacher와 맞는 비율 |
| 누적 수익률 | 초기 자본 대비 최종 평가액 변화 |
| 매도 승률 | 완료된 매수->매도 거래 중 이익 거래 비율 |
| 거래 횟수 | 지나치게 잦은 매매가 아닌지 확인 |
| 시각 리뷰 | 차트상 급등락, 수렴, MA 방향과 모델 행동이 맞는지 확인 |
python main_llm_db_rl_viewer.py
# 설정
# USE_PNG_STATUS_ACTION_SUPERVISED = True
# PNG_STATUS_SHOW_VS_ACTION_CSV_EVAL = True
# PNG_STATUS_EVAL_ACTION_CSV_PATH = "cache/ACTION_repaired.CSV"5년치 데이터에서 정책을 만들 때의 분할
5년치 전체 데이터를 한 번에 학습하고 같은 구간에서만 검증하면 좋은 결과가 나와도 믿기 어렵다. 시간 순서가 있는 금융 데이터는 무작위 섞기보다 시간 기준 분할이 중요하다. 예를 들어 앞의 3년은 정책 생성과 학습, 다음 1년은 검증, 마지막 1년은 완전한 홀드아웃 테스트로 둔다.
| 구간 | 역할 |
|---|---|
| 1~3년차 | 지표 계산, 극단 라벨 생성, teacher 정책 조정, CNN 기반 시각 정책망 학습 |
| 4년차 | 파라미터 튜닝과 모의 포트폴리오 검증 |
| 5년차 | 마지막 홀드아웃 테스트. 이 구간의 결과는 정책 수정 전에 기록한다. |
특히 build_action_from_labels.py의 repair-pnl 옵션은 미래 가격을 참고하는 사후 보정이 포함될 수 있다. 따라서 이 기능은 '정책 교정 실험'으로 구분한다. 실제 검증에는 미래를 보지 않는 walk-forward 방식의 정책을 별도로 사용해야 한다.
VLM과 LLM의 역할 분리
이 프로젝트에서 CNN 기반 시각 정책망은 차트 이미지와 숫자 특징을 보고 행동을 고른다. 반면 일반 LLM은 정책 문서, 근거 설명, 거래 회고, 오류 분석에 더 잘 맞는다. 즉, VLM은 보는 모델이고 LLM은 말로 정리하는 모델이다. 둘을 같은 위치에 두면 판단이 흔들리기 쉬우므로 역할을 나누는 편이 안전하다.
| 모델 | 역할 |
|---|---|
| CNN 기반 시각 정책망 | 차트 PNG와 포트폴리오 상태를 보고 매도/매수/유지 로짓을 낸다. |
| Qwen/Gemma VLM | 차트를 보고 참고 의견과 근거를 생성한다. 체결의 유일한 근거로 쓰지 않는다. |
| LLM | 정책 문서 요약, 학습 로그 해석, 매매 회고, 리스크 설명을 맡는다. |
| 규칙 엔진 | 수수료, 최소 보유 봉, 손절 제한, 위험 구간 차단 같은 안전 장치를 담당한다. |
실전 연결 전 안전 장치
- 실거래 전에 반드시 모의 포트폴리오와 홀드아웃 데이터 검증을 통과해야 한다.
- API 키와 계좌 정보는 코드에 넣지 않고 private 설정과 환경변수로 분리한다.
- 하루 최대 손실, 1회 최대 주문 금액, 연속 손실 중지 조건을 코드로 강제한다.
- VLM 출력은 매수 권유가 아니라 정책 신호로 기록하고, 모든 체결은 로그로 남긴다.
- 수익률만 보지 말고 최대 낙폭, 거래 빈도, 수수료 후 손익, 슬리피지를 함께 본다.
- 사후 보정으로 만든 ACTION은 실전 성능으로 착각하지 않는다.
지금 코드: 차트를 읽고 모의로 확인하기
green은 정답 CSV를 외우는 작은 CNN이 아니다. Market.py가 DB의 과거 캔들을 읽고, ChartImageGenerator.py가 캔들, MA20, MA60, 거래량이 보이는 차트 이미지를 만든다. 비전 모델은 그 이미지를 보고 매수, 매도, 유지 중 하나를 말한다. 로컬에서는 Qwen2.5-VL, Apple Silicon에서는 MLX, 한 장만 물어볼 때는 Gemma 4를 쓸 수 있다. Ollama로 같은 뷰어를 띄우는 입구도 있다.
모의 체결에 그대로 쓰는 신호는 모델의 문장이 아니다. 빨간 선인 MA20을 종가가 위로 돌파하면 매수, 아래로 돌파하면 매도다. 파란 선인 MA60이 내려가는 동안에는 매도 뒤 유지만 하고, MA60이 올라갈 때만 매수와 매도를 연다. 화면에는 최종 판단, LLM 판단, MA 판단을 같이 보여 준다. 그래서 모델이 차트에서 무엇을 말했는지와, 규칙이 실제로 무엇을 체결했는지를 분리해서 볼 수 있다.
| viewer.py | 차트, 세 가지 판단, 모의 포트폴리오를 한 화면에 둔다. |
| main_llm_db_test_ollama_viewer.py | Ollama 비전 모델로 과거 차트를 재생한다. |
| main_llm_db_test_local_viewer.py | 로컬 torch 또는 MLX 모델로 같은 재생을 한다. |
| test_hf_img.py | 차트 질문 프롬프트와 매수/매도/유지 파싱을 맡는다. |
| run_gemma4_chart_llm_only.py | 포트폴리오 없이 차트 한 장에 대한 Gemma 4 의견만 출력한다. |
| main_llm_real.py | 같은 차트 의견을 실시간 시세와 거래소 계좌 코드에 연결할 수 있다. 이 장의 실습 경로는 아니다. |
Market.get_data()
-> ChartImageGenerator
-> 비전 모델: 의견 매수|매도|유지
-> MA20 돌파와 MA60 기울기
-> 모의 포트폴리오계좌 키와 데이터베이스 비밀번호는 소스에 두지 않는다. 실시간 시세 주소처럼 보이는 값도 가려 두었다. 공부할 때는 모의 뷰어에서 모델의 말과 이동평균의 체결이 언제 갈라지는지를 보는 것으로 충분하다.
마무리
이 장의 핵심은 'VLM이 차트를 보고 바로 돈을 번다'가 아니다. red에서는 사람이 정책을 먼저 만들고, 차트 이미지를 보는 모델이 그 정책을 따라 하게 했다. green에서는 비전 모델이 차트를 읽어 의견을 내고, 모의 체결에 쓰는 신호는 이동평균 규칙이 다시 걸러 낸다. 어느 쪽이든 모델의 문장을 곧 주문으로 보내지 않는다.
연습 문제
- market_extreme_labels.csv의 라벨 분포를 다시 계산하고 crash와 surge 구간의 차트를 직접 확인해 보자.
- teacher 모드와 regime 모드로 ACTION CSV를 각각 만들고 매수/매도 횟수를 비교해 보자.
- ACTION_repaired.CSV 없이 teacher 원본 CSV로 PNG 정책을 학습하면 loss와 모의 수익률이 어떻게 달라지는지 비교해 보자.
- 5년치 데이터를 3년/1년/1년으로 나누는 walk-forward 검증 스크립트를 설계해 보자.
- VLM의 판단 근거와 teacher 정책이 충돌하는 구간을 찾아 정책 문서를 개선해 보자.
댓글
0개아직 댓글이 없습니다.