192 lines
8.8 KiB
C#
192 lines
8.8 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|