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();
}
}