// +------------------------------------------------------------------+
// | 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);
}
}
}