2024-05-15 17:13:41 +00:00
|
|
|
#region Using declarations
|
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.ComponentModel;
|
|
|
|
using System.ComponentModel.DataAnnotations;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Text;
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
using System.Windows;
|
|
|
|
using System.Windows.Input;
|
|
|
|
using System.Windows.Media;
|
|
|
|
using System.Xml.Serialization;
|
|
|
|
using NinjaTrader.Cbi;
|
|
|
|
using NinjaTrader.Gui;
|
|
|
|
using NinjaTrader.Gui.Chart;
|
|
|
|
using NinjaTrader.Gui.SuperDom;
|
|
|
|
using NinjaTrader.Gui.Tools;
|
|
|
|
using NinjaTrader.Data;
|
|
|
|
using NinjaTrader.NinjaScript;
|
|
|
|
using NinjaTrader.Core.FloatingPoint;
|
|
|
|
using NinjaTrader.NinjaScript.DrawingTools;
|
|
|
|
using System.Net.Http;
|
|
|
|
using SharpDX.DirectWrite;
|
|
|
|
using SharpDX;
|
|
|
|
using System.Xml.Linq;
|
|
|
|
using System.Xml;
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
using System.Windows.Markup;
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
//This namespace holds Indicators in this folder and is required. Do not change it.
|
|
|
|
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
|
|
|
|
{
|
|
|
|
// TODO: Date should be determined programatically.
|
|
|
|
private string newsUrl = "https://www.forexfactory.com/calendar?day=" + DateTime.Now.ToString("MMMdd.yyyy").ToLower();
|
|
|
|
|
|
|
|
private List<NewsEvent> newsEvents = new List<NewsEvent>();
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
else if (State == State.Configure)
|
|
|
|
{
|
|
|
|
RequestNewsEvents();
|
|
|
|
}
|
|
|
|
else if (State == State.Historical)
|
|
|
|
{
|
|
|
|
SetZOrder(-1); // Display behind bars on chart.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private async void RequestNewsEvents()
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
HttpClient client = new HttpClient();
|
|
|
|
client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
|
|
|
|
|
|
|
|
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 html)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
// Regex to match the rows with class "calendar__row"
|
|
|
|
var rowRegex = new Regex(@"<tr[^>]*class=""[^""]*calendar__row[^""]*""[^>]*>(.*?)</tr>", RegexOptions.Singleline);
|
|
|
|
|
|
|
|
// Regex to extract time and title from the row
|
|
|
|
var timeRegex = new Regex(@"<td[^>]*class=""[^""]*calendar__time[^""]*""[^>]*>(.*?)</td>", RegexOptions.Singleline);
|
|
|
|
var currencyRegex = new Regex(@"<td[^>]*class=""[^""]*calendar__currency[^""]*""[^>]*>(.*?)</td>", RegexOptions.Singleline);
|
|
|
|
var titleRegex = new Regex(@"<span[^>]*class=""[^""]*calendar__event-title[^""]*""[^>]*>(.*?)</span>", RegexOptions.Singleline);
|
|
|
|
|
|
|
|
var rowMatches = rowRegex.Matches(html);
|
|
|
|
foreach (Match rowMatch in rowMatches)
|
|
|
|
{
|
|
|
|
string rowContent = rowMatch.Groups[1].Value;
|
|
|
|
|
|
|
|
var timeMatch = timeRegex.Match(rowContent);
|
|
|
|
var currencyMatch = currencyRegex.Match(rowContent);
|
|
|
|
var titleMatch = titleRegex.Match(rowContent);
|
|
|
|
|
|
|
|
if (timeMatch.Success && titleMatch.Success)
|
|
|
|
{
|
|
|
|
string time = timeMatch.Groups[1].Value.Trim();
|
|
|
|
string currency = currencyMatch.Groups[1].Value.Trim();
|
|
|
|
string title = titleMatch.Groups[1].Value.Trim();
|
|
|
|
|
|
|
|
// Remove any extraneous HTML tags from the time string
|
|
|
|
time = Regex.Replace(time, "<.*?>", string.Empty);
|
|
|
|
|
2024-05-15 17:47:30 +00:00
|
|
|
// Convert the time to the user's time zone
|
|
|
|
time = ConvertToUserTimeZone(time);
|
|
|
|
|
2024-05-15 17:13:41 +00:00
|
|
|
newsEvents.Add(new NewsEvent { Time = time, Currency = currency, Title = title });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
Print("Error parsing news events: " + ex.Message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-15 17:47:30 +00:00
|
|
|
private string ConvertToUserTimeZone(string time)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
// Assumed to be in US Eastern Time
|
|
|
|
DateTime easternTime = DateTime.ParseExact(time, "h:mmtt", System.Globalization.CultureInfo.InvariantCulture);
|
|
|
|
TimeZoneInfo easternTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
|
|
|
|
|
|
|
|
DateTime userTime = TimeZoneInfo.ConvertTime(easternTime, easternTimeZone, Core.Globals.GeneralOptions.TimeZoneInfo);
|
|
|
|
|
|
|
|
// Format the time as "hh:mmtt" (e.g., "06:30am")
|
|
|
|
return userTime.ToString("hh:mmtt").ToLower();
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
Print("Error converting time: " + ex.Message);
|
|
|
|
return time; // Return original time if conversion fails
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-15 17:13:41 +00:00
|
|
|
protected override void OnBarUpdate() { }
|
|
|
|
|
|
|
|
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 newsEvents)
|
|
|
|
{
|
|
|
|
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 newsEvents)
|
|
|
|
{
|
|
|
|
var timeSize = MeasureString(newsEvent.Time, eventTextFormat);
|
|
|
|
var currencySize = MeasureString(newsEvent.Currency, eventTextFormat);
|
|
|
|
var titleSize = MeasureString(newsEvent.Title, eventTextFormat);
|
|
|
|
|
|
|
|
RectangleF timeRect = new RectangleF(x, y, maxColWidth, rowHeight);
|
|
|
|
DrawText(newsEvent.Time, x + (maxColWidth - timeSize.Width) / 2, y + (rowHeight - timeSize.Height) / 2, EventTextColor, eventTextFormat, rowHeight);
|
|
|
|
|
|
|
|
x += maxColWidth;
|
|
|
|
|
|
|
|
RectangleF currencyRect = new RectangleF(x, y, maxColWidth, rowHeight);
|
|
|
|
DrawText(newsEvent.Currency, x + (maxColWidth - currencySize.Width) / 2, y + (rowHeight - currencySize.Height) / 2, EventTextColor, eventTextFormat, rowHeight);
|
|
|
|
|
|
|
|
x += maxColWidth;
|
|
|
|
|
|
|
|
RectangleF titleRect = new RectangleF(x, y, maxColWidth, rowHeight);
|
|
|
|
DrawText(newsEvent.Title, x + (maxColWidth - titleSize.Width) / 2, y + (rowHeight - titleSize.Height) / 2, EventTextColor, eventTextFormat, rowHeight);
|
|
|
|
|
|
|
|
x = ChartPanel.W - (maxColWidth * 3) - HorizontalPadding;
|
|
|
|
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
|
|
|
|
[NinjaScriptProperty]
|
|
|
|
[XmlIgnore]
|
|
|
|
[Display(Name = "Header Row", GroupName = "News", Order = 1)]
|
|
|
|
public Brush HeaderColor { get; set; }
|
|
|
|
|
|
|
|
[NinjaScriptProperty]
|
|
|
|
[XmlIgnore]
|
|
|
|
[Display(Name = "Header Text", GroupName = "News", Order = 2)]
|
|
|
|
public Brush HeaderTextColor { get; set; }
|
|
|
|
|
|
|
|
[NinjaScriptProperty]
|
|
|
|
[XmlIgnore]
|
|
|
|
[Display(Name = "Event Text", GroupName = "News", Order = 3)]
|
|
|
|
public Brush EventTextColor { get; set; }
|
|
|
|
|
|
|
|
[NinjaScriptProperty]
|
|
|
|
[Range(0, int.MaxValue)]
|
|
|
|
[Display(Name = "Horizontal Padding", GroupName = "News", Order = 4)]
|
|
|
|
public int HorizontalPadding { get; set; }
|
|
|
|
|
|
|
|
[NinjaScriptProperty]
|
|
|
|
[Range(0, int.MaxValue)]
|
|
|
|
[Display(Name = "Vertical Padding", GroupName = "News", Order = 5)]
|
|
|
|
public int VerticalPadding { get; set; }
|
|
|
|
|
|
|
|
[NinjaScriptProperty]
|
|
|
|
[Range(0, int.MaxValue)]
|
|
|
|
[Display(Name = "Y-Offset", GroupName = "News", Order = 6)]
|
|
|
|
public int YOffset { get; set; }
|
|
|
|
|
|
|
|
[NinjaScriptProperty]
|
|
|
|
[Display(Name = "Header Font", GroupName = "News", Order = 7)]
|
|
|
|
public SimpleFont HeaderFont { get; set; }
|
|
|
|
|
|
|
|
[NinjaScriptProperty]
|
|
|
|
[Display(Name = "Event Font", GroupName = "News", Order = 8)]
|
|
|
|
public SimpleFont EventFont { 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(Brush headerColor, Brush headerTextColor, Brush eventTextColor, int horizontalPadding, int verticalPadding, int yOffset, SimpleFont headerFont, SimpleFont eventFont)
|
|
|
|
{
|
|
|
|
return News(Input, headerColor, headerTextColor, eventTextColor, horizontalPadding, verticalPadding, yOffset, headerFont, eventFont);
|
|
|
|
}
|
|
|
|
|
|
|
|
public News News(ISeries<double> input, Brush headerColor, Brush headerTextColor, Brush eventTextColor, int horizontalPadding, int verticalPadding, int yOffset, SimpleFont headerFont, SimpleFont eventFont)
|
|
|
|
{
|
|
|
|
if (cacheNews != null)
|
|
|
|
for (int idx = 0; idx < cacheNews.Length; idx++)
|
|
|
|
if (cacheNews[idx] != null && cacheNews[idx].HeaderColor == headerColor && cacheNews[idx].HeaderTextColor == headerTextColor && cacheNews[idx].EventTextColor == eventTextColor && cacheNews[idx].HorizontalPadding == horizontalPadding && cacheNews[idx].VerticalPadding == verticalPadding && cacheNews[idx].YOffset == yOffset && cacheNews[idx].HeaderFont == headerFont && cacheNews[idx].EventFont == eventFont && cacheNews[idx].EqualsInput(input))
|
|
|
|
return cacheNews[idx];
|
|
|
|
return CacheIndicator<News>(new News(){ HeaderColor = headerColor, HeaderTextColor = headerTextColor, EventTextColor = eventTextColor, HorizontalPadding = horizontalPadding, VerticalPadding = verticalPadding, YOffset = yOffset, HeaderFont = headerFont, EventFont = eventFont }, input, ref cacheNews);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns
|
|
|
|
{
|
|
|
|
public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase
|
|
|
|
{
|
|
|
|
public Indicators.News News(Brush headerColor, Brush headerTextColor, Brush eventTextColor, int horizontalPadding, int verticalPadding, int yOffset, SimpleFont headerFont, SimpleFont eventFont)
|
|
|
|
{
|
|
|
|
return indicator.News(Input, headerColor, headerTextColor, eventTextColor, horizontalPadding, verticalPadding, yOffset, headerFont, eventFont);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Indicators.News News(ISeries<double> input , Brush headerColor, Brush headerTextColor, Brush eventTextColor, int horizontalPadding, int verticalPadding, int yOffset, SimpleFont headerFont, SimpleFont eventFont)
|
|
|
|
{
|
|
|
|
return indicator.News(input, headerColor, headerTextColor, eventTextColor, horizontalPadding, verticalPadding, yOffset, headerFont, eventFont);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace NinjaTrader.NinjaScript.Strategies
|
|
|
|
{
|
|
|
|
public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase
|
|
|
|
{
|
|
|
|
public Indicators.News News(Brush headerColor, Brush headerTextColor, Brush eventTextColor, int horizontalPadding, int verticalPadding, int yOffset, SimpleFont headerFont, SimpleFont eventFont)
|
|
|
|
{
|
|
|
|
return indicator.News(Input, headerColor, headerTextColor, eventTextColor, horizontalPadding, verticalPadding, yOffset, headerFont, eventFont);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Indicators.News News(ISeries<double> input , Brush headerColor, Brush headerTextColor, Brush eventTextColor, int horizontalPadding, int verticalPadding, int yOffset, SimpleFont headerFont, SimpleFont eventFont)
|
|
|
|
{
|
|
|
|
return indicator.News(input, headerColor, headerTextColor, eventTextColor, horizontalPadding, verticalPadding, yOffset, headerFont, eventFont);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|