using SignalsTest; using System; using SixLabors.Fonts; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Drawing; using SixLabors.ImageSharp.Drawing.Processing; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using Newtonsoft.Json; using System.Diagnostics; using BinanceExchange.API.Enums; public class Picasso{ public static void DrawChart(List reports, string filename="test", KlineInterval interval = KlineInterval.FifteenMinutes){ // float height = (float)Math.Ceiling(max - min) * 2f; // float width = ((float)height / 9f) * 16f; decimal max = 0; decimal min = 10000000000000; decimal volMax =0; foreach(TAReport report in reports){ if(max < report.High){ max = report.High; } if(min > report.Low){ min = report.Low; } if(volMax < report.candle.Volume){ volMax = report.candle.Volume; } } decimal heightRange = max-min; float height=1080; float width = 1920; float widthMultiplier = width / (float)reports.Count; float heightMultiplier = (height/ (float)heightRange) * 0.6f; 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 for(int i=0; i < reports.Count; i++){ // img.Mutate(ctx=> ctx.DrawLine())) PointF[] points = new PointF[2]; 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 = 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); rangePoints[1] = new PointF(i * widthMultiplier, lowVal); PointF[] volumePoints = new PointF[2]; 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; // Traditional Bar Color rsiColor = reports[i].RSI50 > 50 ? Color.Green : Color.Red; // RSI Bar // Console.WriteLine(reports[i].RSI); img.Mutate(ctx=> ctx. DrawLine(candleColor, 3, rangePoints). DrawLine(candleColor, 10, points). DrawLine(rsiColor, 8, points). DrawLine(candleColor, (reports[i].RelativeVolumeIndex > 1 ? 12: 5), volumePoints) ); #endregion float squareOffset = 2; PointF[] bottomPoints = new PointF[2]; 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); topPoints[1] = new PointF(i * widthMultiplier, highVal+15); #region TWS if(reports[i].TWS > 1){ Color TWSColor = Color.Blue; if(reports[i].TWS == 2){ TWSColor = Color.Cyan; }else if(reports[i].TWS== 3){ TWSColor = Color.White; } float TWSHeightIncrement = Confirmations.GetTWSConfirmation(reports,i); string strengthText = $"+{TWSHeightIncrement}"; if(TWSHeightIncrement==0){strengthText="";} 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 #region TBC if(reports[i].TBC > 1){ Color TBCColor = Color.Red; if(reports[i].TBC == 2){ TBCColor = Color.Cyan; }else if(reports[i].TBC== 3){ TBCColor = Color.White; } float TWSHeightIncrement = Confirmations.GetTBCConfirmation(reports,i); string strengthText = $"+{TWSHeightIncrement}"; if(TWSHeightIncrement==0){strengthText="";} 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 (reports[i].isHammer) { float squareSize = 3f; 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 (reports[i].isInverseHammer) { float squareSize = 3f; PointF squarePosition = new PointF(i * widthMultiplier - squareSize/2, highVal + 50); img.Mutate(ctx => ctx.Fill(Color.White, new RectangleF(squarePosition, new SizeF(squareSize, squareSize)))); } // if (reports[i].isBullFlag) // { // // Draw a flag symbol // float flagWidth = 10f; // float flagHeight = 15f; // float poleHeight = 25f; // // Position flag below the candle // PointF flagBasePosition = new PointF(i * widthMultiplier, lowVal + 10); // // Draw flag pole // img.Mutate(ctx => ctx.DrawLine( // Color.White, // 1f, // new PointF(flagBasePosition.X, flagBasePosition.Y), // new PointF(flagBasePosition.X, flagBasePosition.Y - poleHeight) // )); // // Draw flag triangle // PointF[] flagPoints = new PointF[] { // new PointF(flagBasePosition.X, flagBasePosition.Y - poleHeight), // new PointF(flagBasePosition.X + flagWidth, flagBasePosition.Y - poleHeight + flagHeight/2), // new PointF(flagBasePosition.X, flagBasePosition.Y - poleHeight + flagHeight) // }; // img.Mutate(ctx => ctx.DrawPolygon(Color.White, 1f, flagPoints)); // } } // Console.WriteLine("Getting paths"); // object t = GetPropByName(reports[reports.Count- 1], "SMA7"); // Console.WriteLine(t); // Console.WriteLine(float.Parse(t.ToString())); #region GUIDES PointF[] Vol20 = new PointF[2]; 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, 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)); #endregion #region LINES List allResPoints = Brian.GetCombinedResPoints(filename); List allSupPoints = Brian.GetCombinedSupPoints(filename); List processedResPoints = Utils.GetAveragePoints(allResPoints, min,max); List processedSupPoints = Utils.GetAveragePoints(allSupPoints,min,max); foreach(decimal point in processedResPoints){ 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 = height - ((float)(point-min) * heightMultiplier + candlesOffset); img.Mutate(ctx=> ctx.DrawLine(Color.Green,1f, [new PointF(0, yVal), new PointF(width, yVal)])); } #endregion #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 vWapWeeklyPath = GetPath(reports, (Utils.GetMinutesForInterval(interval) > 60? "VwapMonthly" : "VwapWeekly") , widthMultiplier, (float)heightMultiplier, (float)min, candlesOffset); 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); #endregion //NewChart int intervalInMins = Utils.GetMinutesForInterval(interval); string intervalText = intervalInMins > (60 * 23) ? $"{intervalInMins/(60*24)}D" : $"{intervalInMins}m"; img.Mutate(ctx => ctx. // Draw(Color.Yellow, 2, sma20Path). // Draw(Color.Purple, 2, sma50Path). // Draw(Color.Cyan, 2, sma200Path). Draw(Color.DarkRed,5, vWapWeeklyPath). // Draw(Color.Green, 3, stPath). Draw(Color.Cyan, 3,stochFast). Draw(Color.Orange, 3, stochK3). DrawText($"{filename}- {intervalText}", titleFont,new Color(Rgba32.ParseHex("#FFFFFF")), new PointF(10,10))); img.Save($"Charts/{filename}{intervalInMins}.png"); } } #region HELPERS 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"); float prevVal = i > 0 ? float.Parse(Utils.GetPropByName(reports[i-1],propName).ToString() ?? "0") : 0; 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); } return builder.Build(); } #endregion }