ninjatrader/indicators/news/News.cs

374 lines
14 KiB
C#

#region Using declarations
using NinjaTrader.Gui;
using NinjaTrader.Gui.Chart;
using NinjaTrader.Gui.Tools;
using SharpDX;
using SharpDX.DirectWrite;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Windows.Media;
using System.Xml.Serialization;
#endregion
namespace NinjaTrader.NinjaScript.Indicators
{
public class NewsEvent
{
public string Time { get; set; }
public string Currency { get; set; }
public string Title { get; set; }
}
public class News : Indicator
{
private List<NewsEvent> newsEvents = new List<NewsEvent>();
private DateTime currentDate = DateTime.Now.Date;
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Description = @"Displays economic news events for the current day on the chart";
Name = "News";
Calculate = Calculate.OnBarClose;
IsOverlay = true;
ScaleJustification = ScaleJustification.Right;
IsSuspendedWhileInactive = true;
HeaderColor = Brushes.Yellow;
HeaderTextColor = Brushes.Black;
EventTextColor = Brushes.White;
HeaderFont = new SimpleFont("Arial", 12);
EventFont = new SimpleFont("Arial", 12);
HorizontalPadding = 20;
VerticalPadding = 5;
YOffset = 0;
CellBackgroundColor = Brushes.Transparent;
CurrencyFilter = "USD";
}
else if (State == State.Configure)
{
RequestNewsEvents();
}
else if (State == State.Historical)
{
SetZOrder(-1); // Display behind bars on chart.
}
}
private async void RequestNewsEvents()
{
try
{
newsEvents.Clear();
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
string newsUrl = "https://nfs.faireconomy.media/ff_calendar_thisweek.json";
HttpResponseMessage response = await client.GetAsync(newsUrl);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
ParseNewsEvents(responseBody);
ForceRefresh();
}
catch (Exception e)
{
Print("Error loading news events: " + e.Message);
}
}
private void ParseNewsEvents(string json)
{
try
{
var events = Regex.Matches(json, @"\{[^}]*\}");
foreach (Match eventMatch in events)
{
string eventString = eventMatch.Value;
string date = ExtractJsonValue(eventString, "date");
string currency = ExtractJsonValue(eventString, "country");
string title = ExtractJsonValue(eventString, "title");
DateTime eventTime = DateTime.Parse(date);
if (eventTime.Date != currentDate)
continue;
string time = eventTime.ToString("hh:mmtt").ToLower();
if (!string.IsNullOrEmpty(CurrencyFilter) && !currency.Equals(CurrencyFilter, StringComparison.OrdinalIgnoreCase))
continue; // Skip events that do not match the filter.
newsEvents.Add(new NewsEvent { Time = time, Currency = currency, Title = title });
}
}
catch (Exception ex)
{
Print("Error parsing news events: " + ex.Message);
}
}
private string ExtractJsonValue(string json, string key)
{
string pattern = $"\"{key}\":\"(.*?)\"";
Match match = Regex.Match(json, pattern);
return match.Success ? match.Groups[1].Value : string.Empty;
}
protected override void OnBarUpdate() {
if (State == State.Realtime)
{
if (DateTime.Now.Date != currentDate)
{
RequestNewsEvents();
currentDate = DateTime.Now.Date;
}
}
}
protected override void OnRender(ChartControl chartControl, ChartScale chartScale)
{
base.OnRender(chartControl, chartScale);
int maxColWidth = 0;
int maxHeaderHeight = 0;
int maxRowHeight = 0;
// Determine maximum column width and row height.
using (TextFormat headerTextFormat = HeaderFont.ToDirectWriteTextFormat(),
eventTextFormat = EventFont.ToDirectWriteTextFormat())
{
var headerSize = MeasureString("Time", headerTextFormat);
maxColWidth = Math.Max(maxColWidth, headerSize.Width);
maxHeaderHeight = Math.Max(maxHeaderHeight, headerSize.Height);
headerSize = MeasureString("Currency", headerTextFormat);
maxColWidth = Math.Max(maxColWidth, headerSize.Width);
headerSize = MeasureString("Event", headerTextFormat);
maxColWidth = Math.Max(maxColWidth, headerSize.Width);
foreach (var newsEvent in new List<NewsEvent>(newsEvents)) // New list in order to avoid concurrent modification
{
var timeSize = MeasureString(newsEvent.Time, eventTextFormat);
var currencySize = MeasureString(newsEvent.Currency, eventTextFormat);
var titleSize = MeasureString(newsEvent.Title, eventTextFormat);
maxColWidth = Math.Max(maxColWidth, Math.Max(timeSize.Width, Math.Max(currencySize.Width, titleSize.Width)));
maxRowHeight = Math.Max(maxRowHeight, Math.Max(timeSize.Height, Math.Max(currencySize.Height, titleSize.Height)));
}
}
maxColWidth += HorizontalPadding * 2;
int headerHeight = maxHeaderHeight + (VerticalPadding * 2);
int rowHeight = maxRowHeight + (VerticalPadding * 2);
int x = ChartPanel.W - (maxColWidth * 3) - HorizontalPadding;
int y = YOffset;
// Draw header row.
using (TextFormat headerTextFormat = HeaderFont.ToDirectWriteTextFormat())
{
string[] headers = { "Time", "Currency", "Event" };
foreach (var header in headers)
{
RectangleF headerRect = new RectangleF(x, y, maxColWidth, headerHeight);
RenderTarget.FillRectangle(headerRect, HeaderColor.ToDxBrush(RenderTarget));
var size = MeasureString(header, headerTextFormat);
int textX = x + (maxColWidth - size.Width) / 2; // Center the text horizontally.
int textY = y + (headerHeight - size.Height) / 2; // Center the text vertically.
DrawText(header, textX, textY, HeaderTextColor, headerTextFormat, headerHeight);
x += maxColWidth;
}
}
// Reset x coordinate for news events.
x = ChartPanel.W - (maxColWidth * 3) - HorizontalPadding;
y += headerHeight;
// Draw news events.
using (TextFormat eventTextFormat = EventFont.ToDirectWriteTextFormat())
{
foreach (var newsEvent in new List<NewsEvent>(newsEvents))
{
var timeSize = MeasureString(newsEvent.Time, eventTextFormat);
var currencySize = MeasureString(newsEvent.Currency, eventTextFormat);
var titleSize = MeasureString(newsEvent.Title, eventTextFormat);
// Draw cell backgrounds
RectangleF timeRect = new RectangleF(x, y, maxColWidth, rowHeight);
RenderTarget.FillRectangle(timeRect, CellBackgroundColor.ToDxBrush(RenderTarget));
RectangleF currencyRect = new RectangleF(x + maxColWidth, y, maxColWidth, rowHeight);
RenderTarget.FillRectangle(currencyRect, CellBackgroundColor.ToDxBrush(RenderTarget));
RectangleF titleRect = new RectangleF(x + (2 * maxColWidth), y, maxColWidth, rowHeight);
RenderTarget.FillRectangle(titleRect, CellBackgroundColor.ToDxBrush(RenderTarget));
// Draw text
DrawText(newsEvent.Time, x + (maxColWidth - timeSize.Width) / 2, y + (rowHeight - timeSize.Height) / 2, EventTextColor, eventTextFormat, rowHeight);
DrawText(newsEvent.Currency, x + maxColWidth + (maxColWidth - currencySize.Width) / 2, y + (rowHeight - currencySize.Height) / 2, EventTextColor, eventTextFormat, rowHeight);
DrawText(newsEvent.Title, x + (2 * maxColWidth) + (maxColWidth - titleSize.Width) / 2, y + (rowHeight - titleSize.Height) / 2, EventTextColor, eventTextFormat, rowHeight);
y += rowHeight;
}
}
}
private Size2 MeasureString(string text, TextFormat textFormat)
{
using (var textLayout = new TextLayout(Core.Globals.DirectWriteFactory, text, textFormat, float.PositiveInfinity, float.PositiveInfinity))
{
return new Size2((int)Math.Ceiling(textLayout.Metrics.Width), (int)Math.Ceiling(textLayout.Metrics.Height));
}
}
private void DrawText(string text, int x, int y, Brush brush, TextFormat textFormat, int height)
{
TextLayout textLayout = new TextLayout(Core.Globals.DirectWriteFactory, text, textFormat, 500, height);
Vector2 textOrigin = new Vector2(x, y);
RenderTarget.DrawTextLayout(textOrigin, textLayout, brush.ToDxBrush(RenderTarget));
textLayout.Dispose();
}
public override string DisplayName
{
get { return Name; }
}
#region Properties
[XmlIgnore]
[Display(Name = "Header Row", GroupName = "News", Order = 1)]
public Brush HeaderColor { get; set; }
[Browsable(false)]
public string HeaderColorSerialization
{
get { return Serialize.BrushToString(HeaderColor); }
set { HeaderColor = Serialize.StringToBrush(value); }
}
[XmlIgnore]
[Display(Name = "Header Text", GroupName = "News", Order = 2)]
public Brush HeaderTextColor { get; set; }
[Browsable(false)]
public string HeaderTextColorSerialization
{
get { return Serialize.BrushToString(HeaderTextColor); }
set { HeaderTextColor = Serialize.StringToBrush(value); }
}
[XmlIgnore]
[Display(Name = "Event Text", GroupName = "News", Order = 3)]
public Brush EventTextColor { get; set; }
[Browsable(false)]
public string EventTextColorSerialization
{
get { return Serialize.BrushToString(EventTextColor); }
set { EventTextColor = Serialize.StringToBrush(value); }
}
[Range(0, int.MaxValue)]
[Display(Name = "Horizontal Padding", GroupName = "News", Order = 4)]
public int HorizontalPadding { get; set; }
[Range(0, int.MaxValue)]
[Display(Name = "Vertical Padding", GroupName = "News", Order = 5)]
public int VerticalPadding { get; set; }
[Range(0, int.MaxValue)]
[Display(Name = "Y-Offset", GroupName = "News", Order = 6)]
public int YOffset { get; set; }
[Display(Name = "Header Font", GroupName = "News", Order = 7)]
public SimpleFont HeaderFont { get; set; }
[Display(Name = "Event Font", GroupName = "News", Order = 8)]
public SimpleFont EventFont { get; set; }
[XmlIgnore]
[Display(Name = "Cell Background", GroupName = "News", Order = 9)]
public Brush CellBackgroundColor { get; set; }
[Browsable(false)]
public string CellBackgroundColorSerialization
{
get { return Serialize.BrushToString(CellBackgroundColor); }
set { CellBackgroundColor = Serialize.StringToBrush(value); }
}
[Display(Name = "Currency Filter", GroupName = "News", Order = 10)]
public string CurrencyFilter { get; set; }
#endregion
}
}
#region NinjaScript generated code. Neither change nor remove.
namespace NinjaTrader.NinjaScript.Indicators
{
public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase
{
private News[] cacheNews;
public News News()
{
return News(Input);
}
public News News(ISeries<double> input)
{
if (cacheNews != null)
for (int idx = 0; idx < cacheNews.Length; idx++)
if (cacheNews[idx] != null && cacheNews[idx].EqualsInput(input))
return cacheNews[idx];
return CacheIndicator<News>(new News(), input, ref cacheNews);
}
}
}
namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns
{
public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase
{
public Indicators.News News()
{
return indicator.News(Input);
}
public Indicators.News News(ISeries<double> input )
{
return indicator.News(input);
}
}
}
namespace NinjaTrader.NinjaScript.Strategies
{
public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase
{
public Indicators.News News()
{
return indicator.News(Input);
}
public Indicators.News News(ISeries<double> input )
{
return indicator.News(input);
}
}
}
#endregion