Files
PineSript/PineScript/PineContext.cs

192 lines
8.8 KiB
C#
Raw Permalink Normal View History

2026-04-25 17:32:49 +09:00
namespace PineScriptV6
{
/// <summary>
/// Pine Script의 시리즈(Series) 개념을 C#으로 구현합니다.
/// - 봉마다 갱신되는 OHLCV 히스토리
/// - 계산된 변수값 저장 (sma72, sma4 등)
/// - 배열 백킹 스토어 (array.new_float 등)
///
/// 시리즈 히스토리는 원형 버퍼(CircularBuffer)를 사용해 Push가 O(1).
/// 기존 List 기반 RemoveAt(0) O(n) 방식 대비 최대 수백 배 빠름.
/// </summary>
public static class PineContext
{
private const int MaxHistory = 600;
/// <summary>현재 실행 중인 봉 인덱스 (0-based). bar_index 변수에 매핑됩니다.</summary>
public static int CurrentBarIndex { get; set; } = 0;
// ════════════════════════════════════════════════════════════════
// 원형 버퍼 — Push O(1), Get O(1)
// ════════════════════════════════════════════════════════════════
internal sealed class CircularBuffer
{
private readonly double[] _data;
private int _head; // 다음 쓰기 위치 (0-based)
private int _count; // 현재 쌓인 원소 수 (최대 _capacity)
private readonly int _capacity;
public CircularBuffer(int capacity)
{
_data = new double[capacity];
_capacity = capacity;
}
/// <summary>현재 쌓인 봉 수</summary>
public int Count => _count;
/// <summary>새 값 추가 — O(1)</summary>
public void Push(double value)
{
_data[_head] = value;
_head = (_head + 1) % _capacity;
if (_count < _capacity) _count++;
}
/// <summary>
/// offset 봉 전 값 반환 — O(1)
/// offset=0: 가장 최근, offset=1: 1봉 전, ...
/// </summary>
public double Get(int offset)
{
if (offset < 0 || offset >= _count) return double.NaN;
// _head - 1 이 가장 최근 쓴 위치
int idx = ((_head - 1 - offset) % _capacity + _capacity) % _capacity;
return _data[idx];
}
}
// ── 시리즈 히스토리 (원형 버퍼) ───────────────────────────────
private static readonly Dictionary<string, CircularBuffer> _series = new();
// ── 현재 봉 변수값 (함수 반환값 저장) ─────────────────────────
private static readonly Dictionary<string, double> _variables = new();
// ── 배열 백킹 스토어 ──────────────────────────────────────────
private static readonly Dictionary<string, List<double>> _floatArrays = new();
private static readonly Dictionary<string, List<int>> _intArrays = new();
private static readonly Dictionary<string, List<bool>> _boolArrays = new();
private static readonly Dictionary<string, List<string>> _strArrays = new();
// ════════════════════════════════════════════════════════════════
// 시리즈 히스토리
// ════════════════════════════════════════════════════════════════
/// <summary>시리즈에 현재 값 추가 (봉 교체 시 자동 호출) — O(1)</summary>
public static void Push(string name, double value)
{
if (!_series.TryGetValue(name, out var buf))
{
buf = new CircularBuffer(MaxHistory);
_series[name] = buf;
}
buf.Push(value);
}
/// <summary>
/// 시리즈의 최근 n개 값을 반환합니다.
/// offset: 0 = 현재 봉, 1 = 이전 봉 (close[1] 같은 경우)
/// 반환 배열 index 0 = 가장 최근 값
/// </summary>
public static double[] GetLast(string name, int n, int offset = 0)
{
if (!_series.TryGetValue(name, out var buf))
return Array.Empty<double>();
int needed = n + offset;
if (buf.Count < needed)
return Array.Empty<double>();
var result = new double[n];
for (int i = 0; i < n; i++)
result[i] = buf.Get(offset + i);
return result;
}
/// <summary>현재 시리즈에 쌓인 봉 수</summary>
public static int BarCount(string name) =>
_series.TryGetValue(name, out var buf) ? buf.Count : 0;
/// <summary>
/// CircularBuffer 직접 반환 — 내부 최적화용.
/// 배열 할당 없이 Get(offset) 으로 단일 값을 읽을 때 사용합니다.
/// </summary>
internal static CircularBuffer? GetBuffer(string name) =>
_series.TryGetValue(name, out var buf) ? buf : null;
// ════════════════════════════════════════════════════════════════
// 변수 스토어 (함수 반환값 캡처)
// ════════════════════════════════════════════════════════════════
/// <summary>
/// 계산된 변수값을 저장합니다.
/// 히스토리에도 함께 Push해서 ta.sma(sma4, 12) 같은 호출도 지원합니다.
/// </summary>
public static void SetVariable(string name, double value)
{
_variables[name] = value;
Push(name, value);
}
public static double? GetVariable(string name) =>
_variables.TryGetValue(name, out double v) ? v : null;
// ════════════════════════════════════════════════════════════════
// 배열 스토어
// ════════════════════════════════════════════════════════════════
public static List<double> GetOrCreateFloat(string key)
{
if (!_floatArrays.TryGetValue(key, out var list))
_floatArrays[key] = list = new List<double>();
return list;
}
public static List<int> GetOrCreateInt(string key)
{
if (!_intArrays.TryGetValue(key, out var list))
_intArrays[key] = list = new List<int>();
return list;
}
public static List<bool> GetOrCreateBool(string key)
{
if (!_boolArrays.TryGetValue(key, out var list))
_boolArrays[key] = list = new List<bool>();
return list;
}
public static List<string> GetOrCreateString(string key)
{
if (!_strArrays.TryGetValue(key, out var list))
_strArrays[key] = list = new List<string>();
return list;
}
// 타입 무관하게 double 리스트로 반환
public static List<double> GetAnyAsFloat(string key)
{
if (_floatArrays.TryGetValue(key, out var fa)) return fa;
if (_intArrays.TryGetValue(key, out var ia))
return ia.Select(x => (double)x).ToList();
return GetOrCreateFloat(key);
}
// ════════════════════════════════════════════════════════════════
// 전체 초기화 (재실행 시 반드시 호출)
// ════════════════════════════════════════════════════════════════
public static void Reset()
{
_series.Clear();
_variables.Clear();
_floatArrays.Clear();
_intArrays.Clear();
_boolArrays.Clear();
_strArrays.Clear();
CurrentBarIndex = 0;
}
}
}