// +------------------------------------------------------------------+ // | FXMacroDataService.cs | // | FXMacroData API helper for NinjaTrader 8 | // | https://fxmacrodata.com | // +------------------------------------------------------------------+ // // INSTALLATION // 1. Copy this file to your NinjaTrader 8 Custom folder: // Documents\NinjaTrader 8\bin\Custom\ // 2. In NinjaTrader open Tools > New NinjaScript Editor, then // File > Compile All to build the project. // 3. In your indicator or strategy: add a using for NinjaTrader.Custom // and call FXMacroDataService.GetIndicator(...) directly. // // QUICK REFERENCE // FXMacroDataService.GetIndicator("usd","inflation") // free, no key // FXMacroDataService.GetIndicator("eur","policy_rate","MY_KEY") // paid currencies // FXMacroDataService.GetCalendar("aud","MY_KEY") // FXMacroDataService.GetForex("usd","jpy") // FXMacroDataService.GetCOT("EUR","MY_KEY") // FXMacroDataService.LastValue(json) // most recent val // FXMacroDataService.JsonDouble(json,"val") // field extractor // FXMacroDataService.JsonString(json,"date") // field extractor // // DOCUMENTATION https://fxmacrodata.com/documentation/ninjatrader // API KEY https://fxmacrodata.com/subscribe // +------------------------------------------------------------------+ using System; using System.Globalization; using System.Net; using System.Text; namespace NinjaTrader.Custom { /// /// Static helper class for accessing the FXMacroData REST API from NinjaTrader 8. /// public static class FXMacroDataService { private const string ApiBase = "https://fxmacrodata.com/api/v1"; private const int TimeoutMs = 15000; // ── Internal helpers ─────────────────────────────────────────────────── private static string WebGet(string url) { try { using (var client = new WebClient()) { client.Encoding = Encoding.UTF8; client.Headers[HttpRequestHeader.Accept] = "application/json"; // Respect the timeout by wrapping in a synchronous task var task = client.DownloadStringTaskAsync(url); if (task.Wait(TimeoutMs)) return task.Result; client.CancelAsync(); return string.Empty; } } catch { return string.Empty; } } private static string BuildUrl(string path, string apiKey = "") { string url = ApiBase + "/" + path; if (!string.IsNullOrEmpty(apiKey)) url += "?api_key=" + Uri.EscapeDataString(apiKey); return url; } // ── Public API methods ───────────────────────────────────────────────── /// /// Fetches a macroeconomic indicator time series. /// /// Lowercase currency code, e.g. "usd", "eur", "gbp". /// Snake_case indicator name, e.g. "inflation", "policy_rate". /// Your FXMacroData API key. Leave empty for free USD endpoints. /// JSON string: {"data":[{"date":"...","val":X,"announcement_datetime":"..."}]} public static string GetIndicator(string currency, string indicator, string apiKey = "") { return WebGet(BuildUrl("announcements/" + currency + "/" + indicator, apiKey)); } /// /// Fetches upcoming release dates for a currency. /// /// Lowercase currency code, e.g. "usd", "eur". /// Your FXMacroData API key. /// JSON string: {"releases":[{"indicator":"...","date":"...","announcement_datetime":"..."}]} public static string GetCalendar(string currency, string apiKey = "") { return WebGet(BuildUrl("calendar/" + currency, apiKey)); } /// /// Fetches FX spot rate history for a currency pair. Free — no API key required. /// /// Lowercase base currency code, e.g. "usd". /// Lowercase quote currency code, e.g. "jpy". /// JSON string: {"data":[{"date":"...","val":X}]} public static string GetForex(string baseCcy, string quoteCcy) { return WebGet(BuildUrl("forex/" + baseCcy + "/" + quoteCcy)); } /// /// Fetches CFTC Commitment of Traders (COT) positioning data. /// /// Uppercase currency code, e.g. "EUR", "GBP", "JPY". /// Your FXMacroData API key. /// JSON string with net non-commercial positioning series. public static string GetCOT(string currency, string apiKey = "") { return WebGet(BuildUrl("cot/" + currency, apiKey)); } // ── JSON helpers ─────────────────────────────────────────────────────── /// /// Returns the most recent non-null "val" from an /announcements/ response. /// Scans all occurrences of "val": and returns the last numeric value found. /// Returns double.NaN when no valid value is found. /// public static double LastValue(string json) { if (string.IsNullOrEmpty(json)) return double.NaN; const string search = "\"val\":"; double last = double.NaN; int pos = 0; while (true) { int found = json.IndexOf(search, pos, StringComparison.Ordinal); if (found < 0) break; int start = found + search.Length; while (start < json.Length && json[start] == ' ') start++; int end = start; while (end < json.Length && json[end] != ',' && json[end] != '}' && json[end] != ']') end++; string tok = json.Substring(start, end - start).Trim(); if (tok != "null" && tok.Length > 0) { double v; if (double.TryParse(tok, NumberStyles.Float, CultureInfo.InvariantCulture, out v)) last = v; } pos = found + search.Length; } return last; } /// /// Extracts a numeric value from a flat JSON object by key name. /// Returns double.NaN when the key is absent or the value is null. /// public static double JsonDouble(string json, string key) { if (string.IsNullOrEmpty(json)) return double.NaN; string search = "\"" + key + "\":"; int found = json.IndexOf(search, StringComparison.Ordinal); if (found < 0) return double.NaN; int start = found + search.Length; while (start < json.Length && json[start] == ' ') start++; int end = start; while (end < json.Length && json[end] != ',' && json[end] != '}' && json[end] != ']') end++; string tok = json.Substring(start, end - start).Trim(); if (tok == "null" || tok.Length == 0) return double.NaN; double v; return double.TryParse(tok, NumberStyles.Float, CultureInfo.InvariantCulture, out v) ? v : double.NaN; } /// /// Extracts a string value from a flat JSON object by key name. /// Returns an empty string when the key is not found. /// public static string JsonString(string json, string key) { if (string.IsNullOrEmpty(json)) return string.Empty; string search = "\"" + key + "\":\""; int found = json.IndexOf(search, StringComparison.Ordinal); if (found < 0) return string.Empty; int start = found + search.Length; int end = json.IndexOf('"', start); if (end < 0) return string.Empty; return json.Substring(start, end - start); } } }