Files
PineSript/PineScript/CsvLoader.cs
2026-04-25 17:32:49 +09:00

90 lines
3.8 KiB
C#

using Serilog;
namespace PineScriptV6
{
/// <summary>
/// OHLCV CSV 파일 로더
/// 지원 형식: time,open,high,low,close,Volume
/// 시간 형식: 2025-01-01T09:00:00+09:00 (ISO 8601) 또는 UTC 무표기
/// 타임존 미표기 시 UTC로 간주 → KST(+09:00) 변환 적용
/// </summary>
public static class CsvLoader
{
public static List<CandleData> Load(string filePath)
{
var result = new List<CandleData>();
if (!File.Exists(filePath))
{
Log.Error("[CsvLoader] 파일을 찾을 수 없습니다: {Path}", filePath);
return result;
}
string[] lines = File.ReadAllLines(filePath);
// 헤더 행 스킵 (첫 번째 줄)
int errorCount = 0;
for (int i = 1; i < lines.Length; i++)
{
string line = lines[i].Trim();
if (string.IsNullOrEmpty(line)) continue;
try
{
CandleData candle = ParseLine(line);
result.Add(candle);
}
catch (Exception ex)
{
errorCount++;
Log.Warning("[CsvLoader] {Line}번째 줄 파싱 실패: {Error} | 내용: {Content}",
i + 1, ex.Message, line);
}
}
Log.Information("[CsvLoader] 로드 완료 | 총 {Total}개 봉 | 실패 {Error}건 | 파일: {Path}",
result.Count, errorCount, filePath);
return result;
}
private static CandleData ParseLine(string line)
{
// CSV는 콤마 구분 — 시간 값에 콤마가 없으므로 단순 Split 사용
string[] cols = line.Split(',');
if (cols.Length < 6)
throw new FormatException($"컬럼 수 부족: {cols.Length}개 (최소 6개 필요)");
// ── 시간 파싱 ───────────────────────────────────────────────────
// 타임존 표기가 없으면 UTC로 간주하고 KST(+09:00)로 변환.
// 이미 +09:00 이 붙어 있으면 ToOffset 이 같은 시각을 KST 표기로 유지.
var rawTime = cols[0].Trim();
var kst = TimeSpan.FromHours(9);
DateTimeOffset parsedTime;
if (DateTimeOffset.TryParse(rawTime, out var dto))
{
// 오프셋이 0(UTC)이거나 미표기(Kind==Unspecified → offset 0으로 파싱됨)이면 UTC 간주 후 KST 변환
parsedTime = dto.Offset == TimeSpan.Zero
? new DateTimeOffset(dto.DateTime, TimeSpan.Zero).ToOffset(kst) // UTC → KST 변환
: dto.ToOffset(kst); // 다른 오프셋이면 KST로 변환
}
else
{
// 파싱 실패 시 예외 발생 (상위 catch에서 처리)
throw new FormatException($"시간 파싱 실패: {rawTime}");
}
return new CandleData
{
Time = parsedTime,
Open = decimal.Parse(cols[1].Trim(), System.Globalization.CultureInfo.InvariantCulture),
High = decimal.Parse(cols[2].Trim(), System.Globalization.CultureInfo.InvariantCulture),
Low = decimal.Parse(cols[3].Trim(), System.Globalization.CultureInfo.InvariantCulture),
Close = decimal.Parse(cols[4].Trim(), System.Globalization.CultureInfo.InvariantCulture),
Volume = decimal.Parse(cols[5].Trim(), System.Globalization.CultureInfo.InvariantCulture),
};
}
}
}