diff --git a/Confirmations.cs b/Confirmations.cs index d1a8ebf..8398ee7 100644 --- a/Confirmations.cs +++ b/Confirmations.cs @@ -2,9 +2,9 @@ using SignalsTest; public static class Confirmations { - public static float GetTWSConfirmation(List reports, int i) + public static int GetTWSConfirmation(List reports, int i) { - float TWSHeightIncrement = 0; + int TWSHeightIncrement = 0; if (i > 22) { @@ -23,8 +23,8 @@ public static class Confirmations } } - if (hasCrossedMA) TWSHeightIncrement += 15; - if (hasCrossedST) TWSHeightIncrement += 15; + if (hasCrossedMA) TWSHeightIncrement += 1; + if (hasCrossedST) TWSHeightIncrement += 1; } return TWSHeightIncrement; diff --git a/Extensions.cs b/Extensions.cs index 16cca5d..4194103 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -15,4 +15,8 @@ public static class Extensions{ public static float getCandleLength(this KlineCandleStickResponse candle){ return (float)Math.Abs(candle.Open-candle.Close); } + + public static float getTotalLength(this KlineCandleStickResponse candle){ + return (float)Math.Abs(candle.High - candle.Low); + } } \ No newline at end of file diff --git a/Patterns.cs b/Patterns.cs index cf6b54a..41403c2 100644 --- a/Patterns.cs +++ b/Patterns.cs @@ -1,4 +1,3 @@ - using BinanceExchange.API.Models.Response; public static class Patterns{ @@ -149,7 +148,53 @@ public static class Patterns{ return true; } - // public static bool isHammer(KlineCandleStickResponse response, float ratioThreshold = 0.3f){ + public static bool isHammer(KlineCandleStickResponse response, float bodyToShadowRatio = 0.3f){ + // Check if the candle has a small body compared to total length + float bodyLength = response.getCandleLength(); + float totalLength = response.getTotalLength(); + + // Body should be small compared to total length + if (bodyLength / totalLength > bodyToShadowRatio) { + return false; + } - // } + // Lower shadow should be at least twice the body length + float lowerShadow = (float)Math.Min(response.Open, response.Close) - (float)response.Low; + if (lowerShadow < bodyLength * 2) { + return false; + } + + // Upper shadow should be minimal (less than 10% of total length) + float upperShadow = (float)(response.High - Math.Max(response.Open, response.Close)); + if (upperShadow > totalLength * 0.1) { + return false; + } + + return true; + } + + public static bool isInverseHammer(KlineCandleStickResponse response, float bodyToShadowRatio = 0.3f){ + // Check if the candle has a small body compared to total length + float bodyLength = response.getCandleLength(); + float totalLength = response.getTotalLength(); + + // Body should be small compared to total length + if (bodyLength / totalLength > bodyToShadowRatio) { + return false; + } + + // Upper shadow should be at least twice the body length + float upperShadow = (float)response.High - (float)Math.Max(response.Open, response.Close); + if (upperShadow < bodyLength * 2) { + return false; + } + + // Lower shadow should be minimal (less than 10% of total length) + float lowerShadow = (float)Math.Min(response.Open, response.Close) - (float)response.Low; + if (lowerShadow > totalLength * 0.1) { + return false; + } + + return true; + } } \ No newline at end of file diff --git a/Picasso.cs b/Picasso.cs index 4f07c7a..00cd6ce 100644 --- a/Picasso.cs +++ b/Picasso.cs @@ -43,6 +43,15 @@ public class Picasso{ float candlesOffset = 0.3f * height; float volumeHeightMultiplier = 0.2f * height; + + FontFamily fontFamily; + float TextFontSize = 64f; + string TextFont = "Noto Sans Mono"; + if (!SystemFonts.TryGet(TextFont, out fontFamily)) + throw new Exception($"Couldn't find font {TextFont}"); + + var titleFont = fontFamily.CreateFont(TextFontSize, FontStyle.Regular); + var indicatorFont = fontFamily.CreateFont(18, FontStyle.Regular); using (Image img = new Image((int)width + 100, (int)height, Color.FromRgb(13,18,25))){ #region CANDLES @@ -50,22 +59,22 @@ public class Picasso{ // img.Mutate(ctx=> ctx.DrawLine())) PointF[] points = new PointF[2]; - float openVal1 = (float)(reports[i].candle.Open - min) * heightMultiplier; - float closeVal1 = (float)(reports[i].candle.Close - min)* heightMultiplier; - points[0] =new PointF(i * widthMultiplier, openVal1 + candlesOffset); - points[1] =new PointF(i * widthMultiplier, closeVal1+ candlesOffset); + float openVal1 = height - ((float)(reports[i].candle.Open - min) * heightMultiplier + candlesOffset); + float closeVal1 = height - ((float)(reports[i].candle.Close - min) * heightMultiplier + candlesOffset); + points[0] = new PointF(i * widthMultiplier, openVal1); + points[1] = new PointF(i * widthMultiplier, closeVal1); PointF[] rangePoints = new PointF[2]; - float highVal = (float)(reports[i].candle.High-min)* heightMultiplier; - float lowVal = (float)(reports[i].candle.Low-min)* heightMultiplier; + float highVal = height - ((float)(reports[i].candle.High-min) * heightMultiplier + candlesOffset); + float lowVal = height - ((float)(reports[i].candle.Low-min) * heightMultiplier + candlesOffset); - rangePoints[0] = new PointF(i * widthMultiplier, highVal+ candlesOffset); - rangePoints[1] = new PointF(i * widthMultiplier, lowVal+ candlesOffset); + rangePoints[0] = new PointF(i * widthMultiplier, highVal); + rangePoints[1] = new PointF(i * widthMultiplier, lowVal); PointF[] volumePoints = new PointF[2]; - volumePoints[0] = new PointF(i * widthMultiplier, 0); - volumePoints[1] = new PointF(i * widthMultiplier, ((float)reports[i].candle.Volume / (float)volMax) * volumeHeightMultiplier); + volumePoints[0] = new PointF(i * widthMultiplier, height); + volumePoints[1] = new PointF(i * widthMultiplier, height - ((float)reports[i].candle.Volume / (float)volMax) * volumeHeightMultiplier); Color candleColor = reports[i].candle.Close > reports[i].candle.Open ? Color.Green : Color.Red; @@ -81,12 +90,12 @@ public class Picasso{ float squareOffset = 2; PointF[] bottomPoints = new PointF[2]; - bottomPoints[0] = new PointF(i * widthMultiplier, lowVal-squareOffset+ candlesOffset); - bottomPoints[1] = new PointF(i * widthMultiplier, lowVal- 15+ candlesOffset); + bottomPoints[0] = new PointF(i * widthMultiplier, lowVal-squareOffset); + bottomPoints[1] = new PointF(i * widthMultiplier, lowVal- 15); PointF[] topPoints = new PointF[2]; - topPoints[0] = new PointF(i * widthMultiplier, highVal+squareOffset+ candlesOffset); - topPoints[1] = new PointF(i * widthMultiplier, highVal+15+ candlesOffset); + topPoints[0] = new PointF(i * widthMultiplier, highVal+squareOffset); + topPoints[1] = new PointF(i * widthMultiplier, highVal+15); #region TWS if(reports[i].TWS > 1){ @@ -98,11 +107,14 @@ public class Picasso{ } float TWSHeightIncrement = Confirmations.GetTWSConfirmation(reports,i); - PointF[] TWSPoints = bottomPoints; - TWSPoints[1]-= new PointF(0,TWSHeightIncrement); - img.Mutate(ctx=>ctx.DrawLine(TWSColor, 10, TWSPoints)); - - img.Mutate(ctx=>ctx.DrawLine(Color.DarkBlue , 0.5f, [new PointF(i * widthMultiplier, 0),new PointF(i * widthMultiplier, height)])); //Draw Vertical guide + string strengthText = reports[i].TWS == 3 ? "+2" : "+1"; + string text = $"TWS{strengthText}"; + + // Position text below the candle + PointF textPosition = new PointF(i * widthMultiplier - 15, lowVal + 20); + + img.Mutate(ctx => ctx.DrawText(text, indicatorFont, TWSColor, textPosition)); + img.Mutate(ctx=>ctx.DrawLine(Color.DarkBlue, 0.5f, [new PointF(i * widthMultiplier, 0), new PointF(i * widthMultiplier, height)])); //Draw Vertical guide } #endregion @@ -114,15 +126,34 @@ public class Picasso{ }else if(reports[i].TBC== 3){ TBCColor = Color.White; } - float TBCHeightIncremenet = Confirmations.GetTBCConfirmation(reports,i); + float TWSHeightIncrement = Confirmations.GetTBCConfirmation(reports,i); - PointF[] TBCPoints = topPoints; - TBCPoints[1]+= new PointF(0,TBCHeightIncremenet); - img.Mutate(ctx=>ctx.DrawLine(TBCColor, 10, TBCPoints)); - - img.Mutate(ctx=>ctx.DrawLine(Color.DarkBlue , 0.5f, [new PointF(i * widthMultiplier, 0),new PointF(i * widthMultiplier, height)])); //Draw Vertical guide + string strengthText = reports[i].TBC == 3 ? "+2" : "+1"; + string text = $"TBC{strengthText}"; + + // Position text above the candle + PointF textPosition = new PointF(i * widthMultiplier - 15, highVal - 40); + + img.Mutate(ctx => ctx.DrawText(text, indicatorFont, TBCColor, textPosition)); + img.Mutate(ctx=>ctx.DrawLine(Color.DarkBlue, 0.5f, [new PointF(i * widthMultiplier, 0), new PointF(i * widthMultiplier, height)])); //Draw Vertical guide } #endregion + + // Update hammer indicator position + if (Patterns.isHammer(reports[i].candle)) + { + float squareSize = 10f; + PointF squarePosition = new PointF(i * widthMultiplier - squareSize/2, lowVal - 40); + img.Mutate(ctx => ctx.Fill(Color.White, new RectangleF(squarePosition, new SizeF(squareSize, squareSize)))); + } + + // Update inverse hammer indicator position + if (Patterns.isInverseHammer(reports[i].candle)) + { + float squareSize = 10f; + PointF squarePosition = new PointF(i * widthMultiplier - squareSize/2, highVal + 50); + img.Mutate(ctx => ctx.Fill(Color.White, new RectangleF(squarePosition, new SizeF(squareSize, squareSize)))); + } } // Console.WriteLine("Getting paths"); // object t = GetPropByName(reports[reports.Count- 1], "SMA7"); @@ -132,12 +163,12 @@ public class Picasso{ #region GUIDES PointF[] Vol20 = new PointF[2]; - Vol20[0] = new PointF(0,volumeHeightMultiplier*0.2f); - Vol20[1] = new PointF(width,volumeHeightMultiplier*0.2f); + Vol20[0] = new PointF(0, height - volumeHeightMultiplier*0.2f); + Vol20[1] = new PointF(width, height - volumeHeightMultiplier*0.2f); PointF[] Vol80 = new PointF[2]; - Vol80[0] = new PointF(0, volumeHeightMultiplier*0.8f); - Vol80[1] = new PointF(width, volumeHeightMultiplier * 0.8f); + Vol80[0] = new PointF(0, height - volumeHeightMultiplier*0.8f); + Vol80[1] = new PointF(width, height - volumeHeightMultiplier*0.8f); img.Mutate(ctx=> ctx.DrawLine(Color.Gray, 1f, Vol20).DrawLine(Color.Gray, 0.5f, Vol80)); @@ -152,14 +183,12 @@ public class Picasso{ List processedSupPoints = Utils.GetAveragePoints(allSupPoints,min,max); foreach(decimal point in processedResPoints){ - float yVal = ((float)(point-min) * heightMultiplier) + candlesOffset; - // Console.WriteLine($"{filename} res at {yVal/height}"); + float yVal = height - ((float)(point-min) * heightMultiplier + candlesOffset); img.Mutate(ctx=> ctx.DrawLine(Color.Red,1f, [new PointF(0, yVal), new PointF(width, yVal)])); } foreach(decimal point in processedSupPoints){ - float yVal = ((float)(point-min) * heightMultiplier) + candlesOffset; - // Console.WriteLine($"{filename} sup at {yVal/height}"); + float yVal = height - ((float)(point-min) * heightMultiplier + candlesOffset); img.Mutate(ctx=> ctx.DrawLine(Color.Green,1f, [new PointF(0, yVal), new PointF(width, yVal)])); } #endregion @@ -167,23 +196,17 @@ public class Picasso{ #region OVERLAY LINES //Overlay - Candles - IPath sma20Path = GetPath(reports, "SMA20", widthMultiplier,(float)heightMultiplier, (float)min, candlesOffset); - IPath sma50Path = GetPath(reports, "SMA50", widthMultiplier,(float)heightMultiplier, (float)min, candlesOffset); - IPath sma200Path = GetPath(reports, "SMA200", widthMultiplier,(float)heightMultiplier, (float)min, candlesOffset); + IPath sma20Path = GetPath(reports, "SMA20", widthMultiplier, (float)heightMultiplier, (float)min, candlesOffset); + IPath sma50Path = GetPath(reports, "SMA50", widthMultiplier, (float)heightMultiplier, (float)min, candlesOffset); + IPath sma200Path = GetPath(reports, "SMA200", widthMultiplier, (float)heightMultiplier, (float)min, candlesOffset); - IPath stochFast = GetPath(reports, "Stochastic", widthMultiplier, (float)volumeHeightMultiplier/100f, 0, 0); - IPath stochK3 = GetPath(reports, "StochasticK3", widthMultiplier, (float)volumeHeightMultiplier/100f, 0, 0); + IPath stochFast = GetPath(reports, "Stochastic", widthMultiplier, (float)volumeHeightMultiplier/100f, 0); + IPath stochK3 = GetPath(reports, "StochasticK3", widthMultiplier, (float)volumeHeightMultiplier/100f, 0); - IPath stPath = GetPath(reports, "ST", widthMultiplier,(float)heightMultiplier, (float)min, candlesOffset); + IPath stPath = GetPath(reports, "ST", widthMultiplier, (float)heightMultiplier, (float)min, candlesOffset); #endregion //NewChart - FontFamily fontFamily; - float TextFontSize = 64f; - string TextFont = "Noto Sans Mono"; - if (!SystemFonts.TryGet(TextFont, out fontFamily)) - throw new Exception($"Couldn't find font {TextFont}"); - - var font = fontFamily.CreateFont(TextFontSize, FontStyle.Regular); + int intervalInMins = Utils.GetMinutesForInterval(interval); string intervalText = intervalInMins > (60 * 23) ? $"{intervalInMins/(60*24)}D" : $"{intervalInMins}m"; @@ -193,8 +216,7 @@ public class Picasso{ Draw(Color.Green, 3, stPath). Draw(Color.Cyan, 3,stochFast). Draw(Color.Orange, 3, stochK3). - Flip(FlipMode.Vertical). - DrawText($"{filename}- {intervalText}", font,new Color(Rgba32.ParseHex("#FFFFFF")), new PointF(10,10))); + DrawText($"{filename}- {intervalText}", titleFont,new Color(Rgba32.ParseHex("#FFFFFF")), new PointF(10,10))); img.Save($"Charts/{filename}{intervalInMins}.png"); } @@ -204,17 +226,14 @@ public class Picasso{ static IPath GetPath(List reports, string propName, float widthMultiplier,float heightMultiplier, float min, float offset =0){ PathBuilder builder = new PathBuilder(); builder.SetOrigin(new PointF(0,0)); + float height=1080; for(int i=0; i < reports.Count; i++){ float newVal = float.Parse(Utils.GetPropByName(reports[i],propName).ToString() ?? "0"); - // Console.WriteLine( newVal); - float prevVal = i > 0 ? float.Parse(Utils.GetPropByName(reports[i-1],propName).ToString() ?? "0") : 0; - // Console.WriteLine( prevVal); - float preValY =( prevVal - min) * heightMultiplier ; - float newValY=(newVal - min) * heightMultiplier; - PointF prevPoint = new PointF((i-1)* widthMultiplier, (preValY) +offset); - PointF newPoint = new PointF(i * widthMultiplier, (newValY) + offset); - // Console.WriteLine(newValY); + float preValY = height - ((prevVal - min) * heightMultiplier + offset); + float newValY = height - ((newVal - min) * heightMultiplier + offset); + PointF prevPoint = new PointF((i-1)* widthMultiplier, preValY); + PointF newPoint = new PointF(i * widthMultiplier, newValY); builder.AddLine(prevPoint, newPoint); }