설명
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using System;
namespace cAlgo.Indicators
{
[Indicator(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class DojiStrategyIndicator : Indicator
{
[Parameter("Doji size", DefaultValue = 0.05, MinValue = 0.01, Step = 0.01)]
public double DojiSize { get; set; }
[Parameter("Long Candle Ratio", DefaultValue = 0.7, MaxValue = 1, Step = 0.1)]
public double LongCandleRatio { get; set; }
[Parameter("Use Volume Filter?", DefaultValue = false)]
public bool UseVolumeFilter { get; set; }
[Parameter("Volume Moving Average Period", DefaultValue = 24)]
public int VolumeMA { get; set; }
[Parameter("RSI Period", DefaultValue = 14)]
public int RSIPeriod { get; set; }
[Parameter("Wick-to-Body Ratio", DefaultValue = 2.5, MinValue = 1.0, Step = 0.1)]
public double WickToBodyRatio { get; set; }
private MovingAverage volumeMA;
private RelativeStrengthIndex rsi;
[Output("Doji Signal", Color = Colors.Orange, PlotType = PlotType.Points, Thickness = 2)]
public IndicatorDataSeries DojiSignal { get; set; }
protected override void Initialize()
{
if (UseVolumeFilter)
volumeMA = Indicators.MovingAverage(MarketSeries.TickVolume, VolumeMA, MovingAverageType.Simple);
rsi = Indicators.RelativeStrengthIndex(MarketSeries.Close, RSIPeriod);
}
public override void Calculate(int index)
{
double body = MarketSeries.Close[index] - MarketSeries.Open[index];
double range = MarketSeries.High[index] - MarketSeries.Low[index];
double abody = Math.Abs(body);
double ratio = abody / range;
bool isDoji = abody <= range * DojiSize;
bool goStar = isDoji && (!UseVolumeFilter || MarketSeries.TickVolume[index] > volumeMA.Result[index]);
if (goStar && IsHigherTimeFrame(out int timeFrameNumber, out string label))
{
DojiSignal[index] = MarketSeries.Close[index]; // 차트에 도지 표시
Chart.DrawIcon("Doji" + TimeFrame.ToString() + index, ChartIconType.Diamond, index, MarketSeries.Close[index], Color.Orange);
// 다음 3개의 캔들에 걸쳐 확장되는 실선으로 도지 캔들의 고가와 저가를 강조 표시
HighlightDojiHighLow(index, timeFrameNumber, label);
}
// SMT 다이버전스 감지는 이제 모든 타임프레임에 적용됩니다
DetectSMTDivergence(index);
}
private bool IsHigherTimeFrame(out int timeFrameNumber, out string label)
{
timeFrameNumber = 0;
label = string.Empty;
if (TimeFrame == TimeFrame.Minute15)
{
timeFrameNumber = 15;
label = "0.25";
return true;
}
else if (TimeFrame == TimeFrame.Minute30)
{
timeFrameNumber = 30;
label = "0.5";
return true;
}
else if (TimeFrame == TimeFrame.Minute45)
{
timeFrameNumber = 45;
label = "0.75";
return true;
}
else if (TimeFrame == TimeFrame.Hour)
{
timeFrameNumber = 1;
label = "1";
return true;
}
else if (TimeFrame == TimeFrame.Hour2)
{
timeFrameNumber = 2;
label = "48";
return true;
}
else if (TimeFrame == TimeFrame.Hour4)
{
timeFrameNumber = 4;
label = "4";
return true;
}
else if (TimeFrame == TimeFrame.Daily)
{
timeFrameNumber = 24;
label = "24";
return true;
}
else if (TimeFrame == TimeFrame.Weekly)
{
timeFrameNumber = 168;
label = "W";
return true;
}
else if (TimeFrame == TimeFrame.Monthly)
{
timeFrameNumber = 720;
label = "M";
return true;
}
return false;
}
private void HighlightDojiHighLow(int dojiIndex, int timeFrameNumber, string label)
{
double dojiHigh = MarketSeries.High[dojiIndex];
double dojiLow = MarketSeries.Low[dojiIndex];
// 다음 3개의 캔들에 걸쳐 확장되는 도지 캔들의 고가와 저가에 실선 수평선 그리기
Chart.DrawTrendLine("DojiHighLine" + TimeFrame.ToString() + dojiIndex, dojiIndex, dojiHigh, dojiIndex + 3, dojiHigh, Color.Blue, 2, LineStyle.Solid).IsInteractive = true;
Chart.DrawTrendLine("DojiLowLine" + TimeFrame.ToString() + dojiIndex, dojiIndex, dojiLow, dojiIndex + 3, dojiLow, Color.Red, 2, LineStyle.Solid).IsInteractive = true;
// 파란 선 옆에 타임프레임 번호 또는 라벨 텍스트 추가
Chart.DrawText("TimeFrameHigh" + TimeFrame.ToString() + dojiIndex, label, dojiIndex + 3, dojiHigh, Color.Green).IsInteractive = true;
}
private void DetectSMTDivergence(int index)
{
// 현재 고가 또는 저가가 RSI와 다이버전스를 형성하는지 확인
double currentHigh = MarketSeries.High[index];
double currentLow = MarketSeries.Low[index];
double prevHigh = MarketSeries.High[index - 1];
double prevLow = MarketSeries.Low[index - 1];
double currentRSI = rsi.Result[index];
double prevRSI = rsi.Result[index - 1];
bool isWickDivergence = IsWickDivergence(index, currentHigh, currentLow, prevHigh, prevLow);
if (isWickDivergence)
{
// 약세 다이버전스: 가격은 더 높은 고가를 만들고 RSI는 더 낮은 고가를 만듭니다
if (currentHigh > prevHigh && currentRSI < prevRSI)
{
// 이 타임프레임에 대한 고유 식별자로 차트에 다이버전스 표시
Chart.DrawIcon("BearishDivergence" + TimeFrame.ToString() + index, ChartIconType.DownArrow, index, currentHigh, Color.Red);
}
// 강세 다이버전스: 가격은 더 낮은 저가를 만들고 RSI는 더 높은 저가를 만듭니다
if (currentLow < prevLow && currentRSI > prevRSI)
{
// 이 타임프레임에 대한 고유 식별자로 차트에 다이버전스 표시
Chart.DrawIcon("BullishDivergence" + TimeFrame.ToString() + index, ChartIconType.UpArrow, index, currentLow, Color.Green);
}
}
}
private bool IsWickDivergence(int index, double currentHigh, double currentLow, double prevHigh, double prevLow)
{
double currentBody = Math.Abs(MarketSeries.Close[index] - MarketSeries.Open[index]);
double currentRange = currentHigh - currentLow;
double prevBody = Math.Abs(MarketSeries.Close[index - 1] - MarketSeries.Open[index - 1]);
double prevRange = prevHigh - prevLow;
// 현재 캔들이 작은 몸통과 긴 꼬리를 가지고 있는지 판단
bool currentHasLongWicks = (currentHigh - MarketSeries.Close[index]) > currentBody * WickToBodyRatio &&
(MarketSeries.Open[index] - currentLow) > currentBody * WickToBodyRatio;
// 이전 캔들이 작은 몸통과 긴 꼬리를 가지고 있는지 판단
bool prevHasLongWicks = (prevHigh - MarketSeries.Close[index - 1]) > prevBody * WickToBodyRatio &&
(MarketSeries.Open[index - 1] - prevLow) > prevBody * WickToBodyRatio;
return currentHasLongWicks || prevHasLongWicks;
}
}
}
