90 lines
3.8 KiB
C#
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),
|
|
};
|
|
}
|
|
}
|
|
}
|