namespace Bitget_Server.Application.Indicators; /// /// TradingView 스타일 복합 시그널 (Impulse MACD + MACD + SMA + ADX). /// 기존 WinForms 구현을 그대로 이식하였으며 WinForms using 만 제거. /// public class TradingViewConditionSetup { public int LengthMA = 34; public int LengthSignal = 12; public int AdxLength = 12; public double AdxThreshold = 24; public double RangeThreshold = 1; public double RangeThresholdSell = 1; public List High; public List Low; public List Close; private double[] Sma4; public void SetData(List high, List low, List close) { High = high; Low = low; Close = close; Sma4 = ComputeSma4(); } private double CalcSmma(List src, int len) { double smma = src.Take(len).Average(); for (int i = len; i < src.Count; i++) smma = (smma * (len - 1) + src[i]) / len; return smma; } private double CalcZlema(List src, int length) { double ema1 = Ema(src, length); double ema2 = Ema(Enumerable.Repeat(ema1, src.Count).ToList(), length); double d = ema1 - ema2; return ema1 + d; } private double Ema(List src, int length) { double alpha = 2.0 / (length + 1); double ema = src[0]; for (int i = 1; i < src.Count; i++) ema = alpha * src[i] + (1 - alpha) * ema; return ema; } private double[] ComputeSma4() { double[] sma4 = new double[Close.Count]; for (int i = 3; i < Close.Count; i++) sma4[i] = (Close[i] + Close[i - 1] + Close[i - 2] + Close[i - 3]) / 4.0; return sma4; } private double ComputeAdx() { // 원본에서도 더미값 사용. 필요 시 실제 ADX 구현 추가 가능. return 25.0; } public double ComputeMd(bool flag = true) { if (flag) { double hi = CalcSmma(High, LengthMA); double lo = CalcSmma(Low, LengthMA); var hlc3 = High.Zip(Low, (h, l) => (h + l + Close.Last()) / 3).ToList(); double mi = CalcZlema(hlc3, LengthMA); if (mi > hi) return mi - hi; if (mi < lo) return mi - lo; return 0; } else { var highTemp = new List(High); var lowTemp = new List(Low); var closeTemp = new List(Close); highTemp.RemoveAt(highTemp.Count - 1); lowTemp.RemoveAt(lowTemp.Count - 1); closeTemp.RemoveAt(closeTemp.Count - 1); double hi = CalcSmma(highTemp, LengthMA); double lo = CalcSmma(lowTemp, LengthMA); var hlc3 = highTemp.Zip(lowTemp, (h, l) => (h + l + closeTemp.Last()) / 3).ToList(); double mi = CalcZlema(hlc3, LengthMA); if (mi > hi) return mi - hi; if (mi < lo) return mi - lo; return 0; } } public (double macdLine, double signalLine) ComputeMacd() { double macdLine = Ema(Close, 34) - Ema(Close, 12); double signalLine = Ema(Enumerable.Repeat(macdLine, Close.Count).ToList(), 12); return (macdLine, signalLine); } public (double macdLine, double signalLine) PrevComputeMacd() { var temp = new List(Close); temp.RemoveAt(temp.Count - 1); double macdLine = Ema(temp, 34) - Ema(temp, 12); double signalLine = Ema(Enumerable.Repeat(macdLine, temp.Count).ToList(), 12); return (macdLine, signalLine); } public bool IsRangeBound() { double highestHigh = High.Skip(High.Count - 12).Max(); double lowestLow = Low.Skip(Low.Count - 12).Min(); double range = highestHigh - lowestLow; return range < (highestHigh * RangeThreshold / 100.0); } public bool IsRangeBoundSell() { double highestHigh = High.Skip(High.Count - 12).Max(); double lowestLow = Low.Skip(Low.Count - 12).Min(); double range = highestHigh - lowestLow; return range < (highestHigh * RangeThresholdSell / 100.0); } public bool IsTrending() => ComputeAdx() > AdxThreshold; public double GetAvgData(int range) { var sma = 0.0; for (var i = 0; i < range; i++) sma += Close[Close.Count - (i + 1)]; return sma / range; } public double GetAvgData(int range, int prev) { var sma = 0.0; for (var i = 0; i < range; i++) sma += Close[Close.Count - (i + 1 + prev)]; return sma / range; } public bool IsLongSignal() { double md = ComputeMd(); double prevMd = ComputeMd(false); var (macd, signal) = ComputeMacd(); var (prevMacd, prevSignal) = PrevComputeMacd(); return ((prevMd == 0 && md > 0) || (prevMacd < prevSignal && macd > prevMacd && macd > signal && md > 0)) && IsTrending() && IsSmaMiddle(4, 12, true, GetAvgData(72)) && GetAvgData(4) > GetAvgData(4, 1); } public bool IsShortSignal() { double md = ComputeMd(); double prevMd = ComputeMd(false); var (macd, signal) = ComputeMacd(); var (prevMacd, prevSignal) = PrevComputeMacd(); return ((prevMd == 0 && md < 0) || (prevMacd > prevSignal && macd < prevMacd && macd < signal && md < 0)) && IsTrending() && IsSmaMiddle(4, 12, false, GetAvgData(72)) && GetAvgData(4) < GetAvgData(4, 1); } public bool IsSmaHigh(int range, int count) { var list = new List(); for (int i = Close.Count - count - 1; i < count; i++) { var sum = 0.0; for (int j = 0; j < range; j++) sum += Close[i - j]; list.Add(sum / range); } return list.Max() == list[list.Count - 1]; } public bool IsSmaLow(int range, int count) { var list = new List(); for (int i = Close.Count - count - 1; i < count; i++) { var sum = 0.0; for (int j = 0; j < range; j++) sum += Close[i - j]; list.Add(sum / range); } return list.Min() == list[list.Count - 1]; } public bool IsSmaMiddle(int range, int count, bool longFlag, double value) { var list = new List(); for (int i = Close.Count - count - 1; i < count; i++) { var sum = 0.0; for (int j = 0; j < range; j++) sum += Close[i - j]; list.Add(sum / range); } if (longFlag) return value < list.Average(); return value > list.Average(); } }