coin_alerts/Picasso.cs
2025-03-18 15:17:52 +05:30

305 lines
14 KiB
C#

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<TAReport> 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<Rgba32>((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 VWAP
if(reports[i].isVwapShort){
string text = $"VW(S)";
// Position text below the candle
PointF textPosition = new PointF(i * widthMultiplier - 15, lowVal + 20);
img.Mutate(ctx => ctx.DrawText(text, indicatorFont, Color.Green, textPosition));
img.Mutate(ctx=>ctx.DrawLine(Color.DarkRed, 0.5f, [new PointF(i * widthMultiplier, 0), new PointF(i * widthMultiplier, height)])); //Draw Vertical guide
}
if(reports[i].isVwapLong){
string text = $"VW(L)";
// Position text below the candle
PointF textPosition = new PointF(i * widthMultiplier - 15, lowVal + 20);
img.Mutate(ctx => ctx.DrawText(text, indicatorFont, Color.Green, textPosition));
img.Mutate(ctx=>ctx.DrawLine(Color.DarkGreen, 0.5f, [new PointF(i * widthMultiplier, 0), new PointF(i * widthMultiplier, height)]));
}
#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<decimal> allResPoints = Brian.GetCombinedResPoints(filename);
List<decimal> allSupPoints = Brian.GetCombinedSupPoints(filename);
List<decimal> processedResPoints = Utils.GetAveragePoints(allResPoints, min,max);
List<decimal> 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<TAReport> 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
}