5년치 캔들 데이터로 정책을 만들고, 차트 이미지를 보는 모델로 검증하기

왜 VLM인가

트레이딩 데이터는 숫자이지만, 사람이 시장을 볼 때는 차트를 본다. 캔들, 이동평균, 거래량, MACD, RSI가 한 화면에 놓이면 숫자 표보다 훨씬 빠르게 구조를 파악할 수 있다. 이 장의 아이디어는 바로 그 지점에서 출발한다. 정책은 숫자로 만들고, 모델은 차트 이미지로 그 정책을 배우게 한다.

red 프로젝트는 이 구조를 실험한다. DB에서 캔들 데이터를 가져오고, 이상 구간 라벨을 만들고, 정책 문서의 원칙을 teacher 로짓으로 근사한 뒤, 최종적으로 차트 PNG를 입력받아 매도, 매수, 유지 중 하나를 출력하는 정책 모델을 학습한다.

캔들 DB, 지표 계산, 극단 라벨, 정책 teacher, ACTION CSV, 차트 PNG, CNN 시각 정책망, 모의 포트폴리오로 이어지는 여덟 단계 파이프라인
그림. red 파이프라인은 사람이 만든 정책으로 정답을 만들고, 차트 모델이 그 정답을 따라 하게 한다

아래 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.pyMySQL 캔들 데이터를 읽고 보조지표와 이상탐지 기능을 제공한다.
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.pypolicy.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
현재 라벨 분포개수
normal701,266
volume_spike51,744
price_spike20,110
pattern_anomaly15,597
surge54
crash51
crash_surge_conflict2

3단계: 정책 문서를 teacher로 바꾸기

config/policy.md에는 사람이 이해하는 매매 원칙이 적혀 있다. 예를 들어 왕복 수수료를 고려해 1% 미만의 얕은 익절을 피하고, 장대 양봉은 매수 쪽으로, 장대 음봉은 매도 쪽으로 본다. MA20, MA60, MA120의 수렴과 발산, MA60의 방향, 고점 수렴 리스크, MACD 모멘텀도 함께 본다.

이 자연어 정책은 그대로 학습에 넣을 수 없으므로 rl_policy_teacher.py가 수치 규칙으로 근사한다. 각 봉에서 매도, 매수, 유지 세 액션에 점수를 주고, 가장 높은 점수를 teacher 행동으로 삼는다. 이 방식은 LLM이 무작정 결정하게 하는 것보다 재현성이 좋고, 왜 그런 액션이 나왔는지 설명하기 쉽다.

policy.md의 원칙 중 장대 음봉과 MA60 하향이 적용되어 매도 +2.1, 매수 −1.4, 유지 +0.6 로짓이 나오고 argmax로 매도가 선택되는 예
그림. teacher는 자연어 정책의 원칙들을 세 액션의 점수로 합산한다
정책 원칙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%다. 이처럼 클래스가 불균형하면 항상 유지만 예측해도 전체 일치율이 매우 높을 수 있으므로, 높은 일치율이나 낮은 학습 손실만으로 매매 판단 성능을 주장할 수 없다.

극단 라벨 분포와 ACTION 분포를 로그 눈금 막대로 그리고, 같은 데이터를 선형 눈금으로 보면 유지가 99.9%를 차지함을 보여 주는 그림
그림. 정답 데이터는 '유지'에 극단적으로 치우쳐 있어 일치율만으로는 성능을 말할 수 없다

5단계: CNN 기반 시각 정책망 학습하기

train_png_policy_from_action_csv.py는 ACTION CSV를 teacher로 삼아 차트 PNG를 보는 정책을 학습한다. red 프로젝트는 언어 처리를 하지 않는다. 차트 이미지를 읽는 작은 CNN과 포트폴리오 숫자 특징을 결합해 매도, 매수, 유지 중 하나를 고르는 CNN 기반 시각 정책망이다.

입력은 차트 PNG와 사이드 특징이다. 사이드 특징은 현재 포지션 보유 여부, 미실현 손익, 현금 비율 3개다. 차트만 보면 같은 가격 구조라도 현재 보유 중인지 아닌지를 알 수 없기 때문에 이 보조 정보가 필요하다.

차트 PNG가 CNN을 거치고 보유 여부·미실현 손익·현금 비율 세 숫자와 합쳐져 hidden 256을 지나 매도·매수·유지 로짓 3개를 내는 정책망
그림. CNN 정책망은 차트 그림과 포트폴리오 숫자 3개를 함께 보고 액션을 고른다
입력설명
차트 PNG캔들, 이동평균, 거래량, 보조지표가 그려진 화면 이미지다.
in_position현재 코인을 보유 중인지 여부다.
unrealized_pnl보유 중일 때 매수가 대비 미실현 손익이다.
cash_ratio전체 평가액 중 현금 비율이다.
target actionACTION 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 dimension256
epoch8
CE loss1 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년은 완전한 홀드아웃 테스트로 둔다.

5년 데이터를 1~3년차 학습, 4년차 검증, 5년차 홀드아웃으로 나누고 아래에 walk-forward 방식으로 학습·평가 창을 밀어 가는 그림
그림. 금융 데이터는 무작위가 아니라 시간 순서로 나누어 미래를 보지 않게 한다
구간역할
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 판단을 같이 보여 준다. 그래서 모델이 차트에서 무엇을 말했는지와, 규칙이 실제로 무엇을 체결했는지를 분리해서 볼 수 있다.

캔들 차트 위 MA20·MA60 선과 MA20 상향 돌파 매수, 하향 돌파 매도 표시, MA60 하락 구간, 그리고 LLM 판단·MA 판단·최종 판단 상자
그림. green에서 비전 모델은 의견만 내고, 실제 체결은 이동평균 규칙이 정한다
viewer.py차트, 세 가지 판단, 모의 포트폴리오를 한 화면에 둔다.
main_llm_db_test_ollama_viewer.pyOllama 비전 모델로 과거 차트를 재생한다.
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 정책이 충돌하는 구간을 찾아 정책 문서를 개선해 보자.