Analysis 프로젝트
Pine Script 트레이딩 전략의 파라미터 최적화 도구.
Data 폴더의 SQLite .db 캔들 데이터를 읽어 PineScript 엔진(PineScriptV6)으로 전략을 실행하고,
4개 파라미터 조합별 월간/연간 수익률을 계산한다.
1. 프로젝트 개요
.db 캔들 데이터 → PineScript 엔진 실행 → 시그널 수집 → 수익률 계산 → 콘솔/CSV 출력
- 대상 전략: Script/example.pine — SMA4 밴드 + 일목균형표 구름대 + 횡보(브레이크아웃) 감지 전략
- 최적화 대상 파라미터 4개:
| 변수 | 기본값 | 역할 | 중요도 |
|---|---|---|---|
lengthWindow |
12 | longRatio 계산 시 최근 N봉 최저가 참조 창 | ★★ |
rangePeriod |
240 | longRatio의 SMA 기간 → Long 진입 임계값 결정 | ★★★ |
shortLengthWindow |
4 | shortRatio 계산 시 최근 N봉 최고가 참조 창 | ★★ |
shortRangePeriod |
40 | shortRatio의 SMA 기간 → Short 진입 임계값 결정 | ★★★ |
2. 폴더 구조
Analysis/
├─ Analysis.csproj
├─ appsettings.json ← 설정 (DB 경로, 대상 종목, 파라미터 범위)
├─ Program.cs ← 커맨드 라우팅 + 결과 출력
├─ CandleDbReader.cs ← .db 파일 탐색/필터/로드
├─ GridSearchRunner.cs ← 조합 생성, 순차 실행, 파라미터 치환
├─ ProfitCalculator.cs ← 거래 매칭, 월/연 수익률 집계
├─ Script/
│ └─ example.pine ← 분석 대상 Pine Script 전략
├─ Tools/
│ ├─ CandleDbWriter.cs ← 공통 DB 저장 (Saver와 동일 스키마)
│ ├─ TvCsvImporter.cs ← TradingView CSV → .db 변환
│ └─ BitgetDownloader.cs ← Bitget API → .db 다운로드
└─ Results/
└─ grid_results.csv ← grid 실행 시 생성
데이터 폴더 구조
c:\data\{거래소}\{종목유형}\{종목}\{연도}_{타임프레임}.db
예: c:\data\Bitget\USDT-FUTURES\XRPUSDT\2025_30m.db
현재 보유 데이터 (TradingView BITGET:XRPUSDT.P CSV에서 변환):
| 파일 | 캔들 수 | 기간 |
|---|---|---|
2025_30m.db |
17,520개 | 2025-01-01 ~ 2025-12-31 |
2026_30m.db |
7,610개 | 2026-01-01 ~ 2026-06-08 |
3. 실행 방법
cd C:\ProjetCode\PineSript
dotnet run --project Analysis -- <커맨드>
Visual Studio: Analysis를 시작 프로젝트로 설정 → Properties → Debug → Command line arguments 입력 → F5
커맨드 목록
| 커맨드 | 설명 | 예시 |
|---|---|---|
grid |
그리드 서치 (기본) — appsettings.json 범위의 모든 조합 실행 | Analysis grid |
test [LW] [RP] [SLW] [SRP] |
단일 파라미터 테스트 — 연간/월별/개별 거래 내역 상세 출력 | Analysis test 12 240 4 40 |
import-csv <CSV경로> [타임프레임] |
TradingView CSV → .db 변환 | Analysis import-csv "C:\Downloads\XRPUSDT.csv" 30m |
download [타임프레임] [최대일수] |
Bitget API에서 캔들 다운로드 | Analysis download 30m 62 |
출력 형식 (test / grid Top 10 공통)
── LW=12 RP=240 SLW=4 SRP=40 ──
총 수익률: XX.XX% | 거래: N건 (승 X / 패 Y) | 승률: Z%
[ 연간 수익률 ]
년도 수익률% 거래 승 패 승률%
[ 월별 수익률 ]
년-월 수익률% 거래 승 패 승률%
[ 개별 거래 내역 ] ← test 모드에서만
# 방향 진입시간 청산시간 진입가 청산가 수익%
4. 설정 — appsettings.json
{
"Analysis": {
"DbFolder": "c:/data", // 캔들 DB 루트 폴더
"ScriptPath": "./Script/example.pine", // 분석 대상 Pine Script
"OutputCsv": "./Results/grid_results.csv", // grid 결과 저장 경로
"WarmupBars": 300, // 지표 안정화용 워밍업 봉 수
"Target": {
"Exchange": "Bitget",
"ProductType": "USDT-FUTURES",
"Symbol": "XRPUSDT"
},
"Parameters": {
"lengthWindow": { "Min": 4, "Max": 24, "Step": 4 }, // 6개
"rangePeriod": { "Min": 60, "Max": 480, "Step": 60 }, // 8개
"shortLengthWindow": { "Min": 2, "Max": 12, "Step": 2 }, // 6개
"shortRangePeriod": { "Min": 10, "Max": 80, "Step": 10 } // 8개
}
}
}
현재 범위 기준 조합 수: 6 × 8 × 6 × 8 = 2,304개
5. 소스코드 구성
Program.cs
- 커맨드 라우팅 (
grid/test/import-csv/download) LoadTargetCandles()— DB 탐색 → 필터 → 로드 → 시간순 정렬 (grid/test 공통)PrintDetailedResult()— 연간/월별/개별거래 테이블 출력SaveResultsCsv()— 총 수익률 + 월간 상세 + 연간 상세 3개 섹션으로 CSV 저장 (데이터 기간 헤더 포함)
CandleDbReader.cs
FindAllDbFiles()—{거래소}/{종목유형}/{종목}/*.db3단계 구조 재귀 탐색 (루트 직속 .db도 호환 수집)Filter()— 거래소/종목유형/종목 필터 (대소문자 무시)LoadAll()— 캔들 전체 로드. WAL 모드 DB를 ReadOnly로 열면 오류가 나므로 ReadWrite로 열고PRAGMA journal_mode=DELETE전환 (트러블슈팅 §8 참고)
GridSearchRunner.cs
GenerateCombinations()— 4중 루프로 모든 파라미터 조합 생성RunAll()— 조합별 순차 실행. PineScript 엔진이 static 기반이라 멀티스레드 불가 → 순차 실행- 실행 흐름: Regex로 파라미터 치환 → 임시 .pine 파일 생성 →
PineScriptExecutor실행 →TradingSignal.Results수집 → 임시 파일 삭제 - 치환 패턴:
^(lengthWindow\s*=\s*)\d+(Multiline) — 4개 변수 동일 방식 - 10개 조합마다 진행률/ETA 출력
ProfitCalculator.cs
MatchTrades()— 시그널 순회하며Buy→Sell(Long),Short→Cover(Short) 쌍 매칭- 수익률 공식: 롱
(청산가-진입가)/진입가×100, 숏(진입가-청산가)/진입가×100 - 월간/연간 집계는 청산 시각(ExitTime) 기준 그룹화
- 총 수익률 = 개별 거래 수익률의 단순 합산
Tools/CandleDbWriter.cs
- Saver 프로젝트와 동일 스키마로 저장:
CREATE TABLE candles ( open_time INTEGER PRIMARY KEY, -- Unix milliseconds open REAL, high REAL, low REAL, close REAL, volume REAL ); - 연도별로 파일 분리 (
{year}_{interval}.db, 1m은{year}.db) ON CONFLICT(open_time) DO UPDATE— 중복 시 덮어쓰기 (upsert)
Tools/TvCsvImporter.cs
- TradingView 내보내기 CSV 파싱 → CandleDbWriter로 저장
- 시간 포맷 자동 감지: Unix seconds / Unix milliseconds(13자리 이상) / ISO 8601 / 커스텀 포맷
- 헤더 컬럼 자동 매핑 (time/timestamp/date, open, high, low, close, volume)
Tools/BitgetDownloader.cs
- Bitget REST API 2개 엔드포인트 조합:
/api/v2/mix/market/candles— 최근 캔들 최대 1,000건 (30분봉 기준 약 20일)/api/v2/mix/market/history-candles— 요청당 200건 페이징
- 30분봉 기준 현실적 최대 약 62일
- 레이트 리밋 100ms 간격, 중복 제거 후 저장
6. Pine Script 전략 분석
핵심 아이디어
isSidewaysLong / isSidewaysShort는 이름과 달리 "횡보를 벗어나는 순간(브레이크아웃)" 을 감지한다.
즉, "평소보다 변동폭이 커지면 진입을 허용" 하는 구조.
변수 흐름
■ Long 측
longRatio = (high - ta.lowest(low, lengthWindow)) / ta.lowest(low, lengthWindow) * 100
└ 매 봉의 "현재 상승폭 비율" (최근 12봉 최저가 대비)
rangeThresholdLongPercent = ta.sma(longRatio, rangePeriod)
└ 240봉(30분봉 기준 5일) 평균 변동폭 = "보통 변동폭" 기준선
isSidewaysLong = priceRangeLong > (lowestLow × rangeThresholdLongPercent / 100)
and sma4 > 구름대 상단
■ Short 측 (동일 구조, 더 짧은 창)
shortRatio = (ta.highest(high, shortLengthWindow) - low) / ta.highest(high, shortLengthWindow) * 100
rangeThresholdShortPercent = ta.sma(shortRatio, shortRangePeriod) // 40봉 ≈ 20시간
isSidewaysShort = priceRangeShort > (highestHigh × rangeThresholdShortPercent / 100)
and sma4 < 구름대 상단
- Short 창이 Long보다 짧은 이유: 하락이 상승보다 빠르기 때문 — 기준선이 더 빠르게 변해야 함
- 고정 임계값(과거의
rangeThresholdPercent = 2,1.7)이 아니라 시장 상황에 따라 기준선이 자동으로 변함:- 변동성 큰 시기 → 기준 상승 → 진입 까다로움
- 조용한 시기 → 기준 하락 → 작은 움직임에도 진입 허용
예시 계산
high = 2.10, 최근 12봉 최저가 = 2.00
→ longRatio = (2.10 - 2.00) / 2.00 × 100 = 5.0%
최근 240봉의 longRatio 평균이 3.3%라면
→ 현재 가격 움직임이 3.3%를 넘는 순간 브레이크아웃 → 롱 진입 허용
기타 변수
| 변수 | 값 | 비고 |
|---|---|---|
rangeLookback |
12 | 현재 가격범위(highestHigh − lowestLow) 측정 창. 그리드서치 대상 아님 (★ 보조) |
rangeThresholdPercent |
2 | 미사용 — 예전 고정 임계값, ta.sma 동적 계산으로 대체됨 |
rangeShortThresholdPercent |
1.7 | 미사용 — 동일 |
진입/청산 조건 (요약)
- 롱 진입: 12봉 최고가 갱신 + 양봉 + 최근 10봉 내 sma4Low 터치 + isSidewaysLong + sma4 상승 + sma4Middle > sma72 + 구름대 위
- 롱 청산: sma4Middle이 sma72 하향 돌파 또는 sma4 == sma4Low
- 숏 진입: 구름대 아래 + sma4 < sma72 + isSidewaysShort 등 2가지 OR 조건
- 숏 청산: sma4High ≤ sma4 또는 sma4 > sma72 등 4가지 OR 조건
- 상태 변수
inLong/inShort로 중복 진입 방지
7. 데이터 수집 방법
방법 A — TradingView CSV (권장, 1년치 이상 가능)
- TradingView(Premium)에서
BITGET:XRPUSDT.P30분봉 차트 열기.P= Perpetual(무기한 선물) = Bitget USDT-FUTURES XRPUSDT와 동일 계약- Premium은 30분봉 최대 20,000개 바 (약 416일) 제공
- 차트 우클릭 → 테이블 보기 → CSV 다운로드
- 시간 포맷: Unix Timestamp 권장 (ISO도 자동 감지되지만 Unix가 시간대 혼동 없음)
- 변환 실행:
dotnet run --project Analysis -- import-csv "C:\Downloads\BITGET_XRPUSDT.csv" 30m
방법 B — Bitget API 직접 다운로드 (최대 약 62일)
dotnet run --project Analysis -- download 30m 62
8. 트러블슈팅 이력
| 증상 | 원인 | 해결 |
|---|---|---|
SQLite Error 8: attempt to write a readonly database |
WAL 모드 DB를 SqliteOpenMode.ReadOnly로 열면 -shm 파일 생성이 필요해 실패 |
CandleDbReader에서 ReadWrite로 열고 PRAGMA journal_mode=DELETE 전환 |
SQLite Error 1: no such table: candles |
이전 변환 과정에서 DB 파일 손상 (0바이트 / 불완전 데이터) | CSV에서 재변환. journal_mode=DELETE로 생성, -wal/-shm 잔여 파일 정리 |
| 샌드박스 환경에서 SQLite disk I/O error | 마운트 폴더에 직접 DB 생성 불가 | /tmp에 먼저 생성 후 대상 폴더로 복사 |
9. 의존 프로젝트
- PineScriptV6 — Pine Script 실행 엔진 (
PineScriptExecutor,TradingSignal.Results,CandleData,TradeSignalEvent)- static 기반이므로 그리드서치는 순차 실행만 가능 (멀티스레드 불가)
- Saver — 실시간 캔들 수집 프로젝트. CandleDbWriter가 동일한 DB 스키마/폴더 구조를 따름