How to Integrate FXMacroData with TradingView via Pine Script banner image

Implementation

How-To Guides

How to Integrate FXMacroData with TradingView via Pine Script

A step-by-step guide to overlaying FXMacroData macro announcements, policy rates, and COT positioning onto TradingView charts using a Python code generator and Pine Script v5.

TradingView is the charting platform of choice for the majority of retail FX traders, and Pine Script is the language that brings custom indicators and strategy signals to life on those charts. The challenge is that Pine Script runs entirely inside TradingView's sandbox — it cannot make outbound HTTP calls to external APIs at runtime. This guide bridges that gap with a two-part workflow: a small Python script fetches macro announcement data from FXMacroData, generates ready-to-paste Pine Script code, and the resulting indicator overlays those data points directly onto your TradingView chart with event markers, a signal line, and a live macro data table.

What you will build

  • A Python script that fetches policy rate, CPI, and PMI announcements for any currency from FXMacroData and generates Pine Script source code automatically
  • A Pine Script v5 indicator that marks macro event dates on your chart with labelled diamonds, draws a macro-value overlay line, and shows a data table summarising the last several prints
  • A TradingView alert → webhook bridge that pings a small Flask endpoint whenever your Pine Script condition fires, allowing you to cross-reference the signal with fresh FXMacroData context automatically

Prerequisites

  • Python 3.10+ with requests installed
  • FXMacroData API key — sign up at /subscribe; USD indicator data is free with no key required
  • A TradingView account — the free tier supports Pine Script v5 and custom indicators
  • Basic Pine Script familiarity — the Pine Script v5 docs cover the core concepts in under an hour

Step 1 — Understand the FXMacroData announcement endpoint

Every indicator in FXMacroData follows the same REST shape. A policy-rate request for the EUR looks like this — use your API key as a query parameter:

curl "https://fxmacrodata.com/api/v1/announcements/eur/policy_rate?api_key=YOUR_API_KEY&start=2023-01-01"

The JSON response is a flat object with a data array:

{
  "currency": "eur",
  "indicator": "policy_rate",
  "data": [
    {
      "date": "2025-03-06",
      "val": 2.65,
      "announcement_datetime": "2025-03-06T13:15:00Z"
    },
    {
      "date": "2025-01-30",
      "val": 2.90,
      "announcement_datetime": "2025-01-30T13:15:00Z"
    },
    {
      "date": "2024-12-12",
      "val": 3.15,
      "announcement_datetime": "2024-12-12T13:15:00Z"
    }
  ]
}

Each record carries a date (YYYY-MM-DD local date of the release), a numeric val, and a second-level UTC announcement_datetime. The consistent shape across all currencies and indicators is what makes the code generator simple to build. For a full list of available indicators, see the EUR policy rate docs and explore the indicator catalogue for your target currency.


Step 2 — Fetch macro data with Python

Create a file called generate_pine.py. This script fetches announcement data for a chosen currency and indicator, then serialises the results into Pine Script arrays that you paste into the indicator file.

"""
generate_pine.py — fetch FXMacroData announcements and output Pine Script arrays.

Usage:
    python generate_pine.py --currency eur --indicator policy_rate --start 2023-01-01
"""
import argparse
import json
import os
from datetime import datetime, timezone

import requests

BASE_URL = "https://fxmacrodata.com/api/v1"
API_KEY = os.environ.get("FXMD_API_KEY", "")


def fetch_announcements(currency: str, indicator: str, start: str) -> list[dict]:
    url = f"{BASE_URL}/announcements/{currency}/{indicator}"
    params: dict = {"start": start}
    if API_KEY:
        params["api_key"] = API_KEY
    resp = requests.get(url, params=params, timeout=15)
    resp.raise_for_status()
    return resp.json().get("data", [])


def ts_to_pine_timestamp(iso_str: str) -> int:
    """Convert an ISO-8601 UTC string to a Unix timestamp (milliseconds for Pine)."""
    dt = datetime.fromisoformat(iso_str.replace("Z", "+00:00"))
    return int(dt.timestamp() * 1000)


def build_pine_arrays(records: list[dict]) -> str:
    """Render the data as Pine Script array literals."""
    ts_list = []
    val_list = []
    for r in records:
        ann = r.get("announcement_datetime") or f"{r['date']}T00:00:00Z"
        ts_list.append(str(ts_to_pine_timestamp(ann)))
        val_list.append(str(r["val"]))

    ts_str = ", ".join(ts_list)
    val_str = ", ".join(val_list)
    n = len(records)

    return f"""
// ── Auto-generated by generate_pine.py — do not edit manually ──
// Currency: {records[0].get('currency', '?').upper() if records else '?'}
// Indicator: {records[0].get('indicator', '?') if records else '?'}
// Records: {n}

var int[] _ann_ts  = array.from({ts_str})
var float[] _ann_val = array.from({val_str})
"""


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--currency", required=True)
    parser.add_argument("--indicator", required=True)
    parser.add_argument("--start", default="2023-01-01")
    args = parser.parse_args()

    print(f"Fetching {args.indicator} for {args.currency.upper()} from {args.start} …")
    records = fetch_announcements(args.currency, args.indicator, args.start)
    print(f"  {len(records)} records returned.")
    pine = build_pine_arrays(records)
    print(pine)

    output_file = f"pine_data_{args.currency}_{args.indicator}.txt"
    with open(output_file, "w") as f:
        f.write(pine)
    print(f"Arrays written to {output_file}")


if __name__ == "__main__":
    main()

Store your API key and run the script:

export FXMD_API_KEY="YOUR_API_KEY"
python generate_pine.py --currency eur --indicator policy_rate --start 2023-01-01

The output file pine_data_eur_policy_rate.txt will contain Pine Script array literals ready to drop into the indicator. You can run the same script for CPI (--indicator inflation), PMI (--indicator pmi), or any other indicator from the FXMacroData catalogue.


Step 3 — Build the Pine Script indicator

Open TradingView, navigate to the Pine Script editor (the "Pine Editor" tab at the bottom of the chart), and paste the following indicator. Replace the _ann_ts and _ann_val arrays at the top with the output from Step 2.

// @version=5
// FXMacroData Overlay — policy rate event markers + value line
// Replace the arrays below with output from generate_pine.py

indicator("FXMacroData Macro Overlay", overlay=true, max_labels_count=50, max_lines_count=50)

// ── User inputs ──
i_show_labels   = input.bool(true,  "Show event labels")
i_show_line     = input.bool(true,  "Show macro value line")
i_show_table    = input.bool(true,  "Show data table")
i_label_color   = input.color(color.new(color.orange, 0), "Label colour")
i_line_color    = input.color(color.new(color.orange, 30), "Line colour")

// ── Paste generated arrays here ──
// ---- BEGIN GENERATED SECTION ----
var int[]   _ann_ts  = array.from(1672531200000, 1675209600000, 1677628800000)
var float[] _ann_val = array.from(2.50, 2.65, 3.00)
// ---- END GENERATED SECTION ----

// ── Helper: find the most recent announcement value at or before this bar ──
f_current_val() =>
    float result = na
    int bar_t = time
    for i = 0 to array.size(_ann_ts) - 1
        if array.get(_ann_ts, i) <= bar_t
            result := array.get(_ann_val, i)
            break
    result

// ── Macro overlay line ──
float macro_val = f_current_val()
plot(i_show_line ? macro_val : na, title="Macro value", color=i_line_color,
     linewidth=2, style=plot.style_stepline)

// ── Event marker labels ──
if i_show_labels
    for i = 0 to array.size(_ann_ts) - 1
        int   ts  = array.get(_ann_ts, i)
        float val = array.get(_ann_val, i)
        if ts >= chart.left_visible_bar_time and ts <= chart.right_visible_bar_time
            label.new(
                x       = ts,
                y       = high * 1.002,
                text    = str.tostring(val, "#.##"),
                style   = label.style_diamond,
                color   = i_label_color,
                textcolor = color.white,
                size    = size.small,
                xloc    = xloc.bar_time
            )

// ── Summary table (top-right corner) ──
var table t = table.new(position.top_right, 2, 6,
    bgcolor       = color.new(color.navy, 85),
    border_width  = 1,
    border_color  = color.new(color.gray, 60))

if i_show_table and barstate.islast
    table.cell(t, 0, 0, "Date",  text_color=color.silver, text_size=size.small)
    table.cell(t, 1, 0, "Value", text_color=color.silver, text_size=size.small)
    int rows = math.min(5, array.size(_ann_ts))
    for i = 0 to rows - 1
        int   ts  = array.get(_ann_ts, i)
        float val = array.get(_ann_val, i)
        string date_str = str.format("{0,date,yyyy-MM-dd}", ts)
        table.cell(t, 0, i + 1, date_str, text_color=color.white, text_size=size.tiny)
        table.cell(t, 1, i + 1, str.tostring(val, "#.##"), text_color=color.orange, text_size=size.tiny)

Why stepline?

Macro indicator values are step functions — the policy rate does not smoothly interpolate between meetings; it jumps. Using plot.style_stepline keeps the overlay honest: the line holds its level until a new announcement prints, exactly matching the published data.

Click Add to chart. The indicator will overlay a step-line matching the macro value progression and drop a labelled diamond marker at each announcement date. Scroll back through history to see how each policy rate print aligned with the price action on your chosen currency pair.


Step 4 — Layer in multiple indicators

A single indicator tells part of the story. The real edge comes from layering policy rate, CPI, and PMI together. Repeat Step 2 for each indicator and add an additional section to the Pine Script, or create three separate indicators and stack them on the same chart pane.

For a three-indicator fetch in one go, extend the Python script:

INDICATORS = ["policy_rate", "inflation", "pmi"]
CURRENCY   = "eur"
START      = "2023-01-01"

for ind in INDICATORS:
    records = fetch_announcements(CURRENCY, ind, START)
    pine    = build_pine_arrays(records)
    with open(f"pine_data_{CURRENCY}_{ind}.txt", "w") as f:
        f.write(pine)
    print(f"Written pine_data_{CURRENCY}_{ind}.txt  ({len(records)} records)")

Each output file gives you a drop-in section for a separate Pine Script indicator. Add each one to your chart and assign a distinct colour per indicator — for example, orange for policy rate, blue for CPI, and green for PMI — so you can see at a glance which macro regime is in force. You can explore all available indicators for EUR through the EUR PMI docs and the wider indicator catalogue.


Step 5 — Automate updates with a release calendar check

The macro data in the Pine Script arrays becomes stale the moment a new announcement prints. Automate the refresh by calling FXMacroData's release calendar endpoint before each trading session to check whether anything printed since your last regeneration. If it did, re-run the generator and update your Pine Script.

"""
check_and_refresh.py — Re-generate Pine arrays if new announcements have printed.
Run this before each session, e.g. via cron or GitHub Actions.
"""
import subprocess
import requests
import os
from datetime import date, timedelta

BASE_URL = "https://fxmacrodata.com/api/v1"
API_KEY  = os.environ.get("FXMD_API_KEY", "")
PAIRS    = [
    ("eur", "policy_rate"),
    ("eur", "inflation"),
    ("eur", "pmi"),
]

def latest_release_date(currency: str, indicator: str) -> str | None:
    """Return the most recent announcement date for this indicator."""
    params: dict = {"start": str(date.today() - timedelta(days=7))}
    if API_KEY:
        params["api_key"] = API_KEY
    r = requests.get(
        f"{BASE_URL}/announcements/{currency}/{indicator}",
        params=params, timeout=10
    )
    r.raise_for_status()
    data = r.json().get("data", [])
    return data[0]["date"] if data else None

def refresh_all() -> None:
    for currency, indicator in PAIRS:
        latest = latest_release_date(currency, indicator)
        if latest and latest >= str(date.today() - timedelta(days=1)):
            print(f"New print detected: {currency.upper()} {indicator} on {latest}. Regenerating …")
            subprocess.run(
                ["python", "generate_pine.py",
                 "--currency", currency,
                 "--indicator", indicator,
                 "--start", "2023-01-01"],
                check=True,
            )
        else:
            print(f"{currency.upper()} {indicator}: no new prints since yesterday.")

if __name__ == "__main__":
    refresh_all()

Schedule this script to run Monday–Friday at 08:00 UTC, before the London open, using a cron job, a GitHub Actions workflow, or any task scheduler. When a refresh runs, copy the updated pine_data_*.txt content into TradingView's Pine Editor and click Save. Your chart markers will immediately reflect the latest announcement data.


Step 6 — Send TradingView alerts back to FXMacroData context

The workflow so far pushes macro data into TradingView. Step 6 closes the loop in the other direction: when your Pine Script fires a trading signal (e.g. a breakout above resistance after a CPI print), TradingView's webhook alert system forwards that event to a small Flask endpoint, which immediately queries FXMacroData for fresh context — the latest CPI trend, upcoming releases, and current COT positioning — and logs or notifies you with the full macro picture.

Create webhook_server.py:

"""
webhook_server.py — Receive TradingView alert webhooks and enrich with FXMacroData context.
Run with: python webhook_server.py
Expose to the internet via ngrok or deploy to a cloud function / VPS.
"""
import os
import json
from datetime import date, timedelta

import requests
from flask import Flask, request, jsonify

app = Flask(__name__)
BASE_URL  = "https://fxmacrodata.com/api/v1"
API_KEY   = os.environ.get("FXMD_API_KEY", "")

def get_macro_context(currency: str) -> dict:
    """Fetch latest policy rate + CPI for the given currency."""
    params: dict = {"start": str(date.today() - timedelta(days=90))}
    if API_KEY:
        params["api_key"] = API_KEY

    ctx: dict = {}
    for indicator in ("policy_rate", "inflation"):
        try:
            r = requests.get(
                f"{BASE_URL}/announcements/{currency}/{indicator}",
                params=params, timeout=10
            )
            r.raise_for_status()
            data = r.json().get("data", [])
            ctx[indicator] = data[0] if data else {}
        except Exception as exc:
            ctx[indicator] = {"error": str(exc)}
    return ctx

@app.route("/tradingview/alert", methods=["POST"])
def receive_alert():
    payload = request.get_json(force=True, silent=True) or {}
    currency = payload.get("currency", "usd").lower()
    signal   = payload.get("signal", "unknown")

    ctx = get_macro_context(currency)
    result = {
        "received_signal": signal,
        "currency": currency.upper(),
        "macro_context": ctx,
    }
    print(json.dumps(result, indent=2))
    return jsonify(result), 200

if __name__ == "__main__":
    app.run(port=5050, debug=False)

Install Flask and run the server:

pip install flask
python webhook_server.py

Expose the local port to the internet with ngrok:

ngrok http 5050

In TradingView, open Alerts → Create Alert, select your Pine Script condition, enable Webhook URL, and paste the ngrok HTTPS URL ending with /tradingview/alert. Set the alert message to a JSON payload:

{
  "currency": "eur",
  "signal": "breakout_long",
  "ticker": "{{ticker}}",
  "price": {{close}}
}

Every time the Pine Script condition triggers, TradingView posts that JSON to your endpoint. The webhook handler fetches the latest EUR policy rate and CPI from FXMacroData and logs the full macro context alongside the signal. You can extend this to send a Telegram or Slack notification using the enriched data.

Production tip

For a permanent deployment, host webhook_server.py on a cloud VM, Railway, or Render. Use an environment variable for FXMD_API_KEY and add basic HMAC verification on the webhook payload to prevent spoofed alerts. TradingView does not sign webhook payloads natively, so your own shared secret in the URL path (e.g. /tradingview/alert/SECRET_TOKEN) is the simplest guard.


Step 7 — Extend with COT positioning and FX spot context

Macro announcement data is one layer of the picture. For a more complete signal, add CFTC Commitment of Traders positioning and FX spot trend context from FXMacroData.

Fetch COT positioning for EUR futures:

curl "https://fxmacrodata.com/api/v1/cot/eur?api_key=YOUR_API_KEY&start=2024-01-01"
{
  "currency": "eur",
  "data": [
    {
      "date": "2025-03-18",
      "net_noncommercial": 48320,
      "long_noncommercial": 182500,
      "short_noncommercial": 134180
    }
  ]
}

Add a COT net-positioning series to your Pine Script generator using the same pattern as Step 2. A positive net_noncommercial value indicates speculative traders are net long EUR futures — a sentiment backdrop that adds conviction when combined with a hawkish ECB policy signal. Explore COT data via the COT dashboard before embedding it in your indicator.

Extend the Python generator to include a COT section:

def fetch_cot(currency: str, start: str) -> list[dict]:
    url = f"{BASE_URL}/cot/{currency}"
    params: dict = {"start": start}
    if API_KEY:
        params["api_key"] = API_KEY
    resp = requests.get(url, params=params, timeout=15)
    resp.raise_for_status()
    return resp.json().get("data", [])


def build_cot_pine_arrays(records: list[dict]) -> str:
    ts_list  = [str(int(datetime.fromisoformat(r["date"] + "T00:00:00+00:00").timestamp() * 1000)) for r in records]
    net_list = [str(r.get("net_noncommercial", 0)) for r in records]
    ts_str   = ", ".join(ts_list)
    net_str  = ", ".join(net_list)
    return f"""
var int[]   _cot_ts  = array.from({ts_str})
var float[] _cot_net = array.from({net_str})
"""

In Pine Script, plot _cot_net in a separate pane (set overlay=false for that indicator) so you can see speculative positioning trend alongside price and macro announcements.


What you built

  • ✅ A Python script that fetches FXMacroData announcements and generates Pine Script array literals
  • ✅ A Pine Script v5 indicator that marks macro event dates on your TradingView chart with labels, a step-line overlay, and a data table
  • ✅ An automated refresh script that checks for new prints before each session and regenerates the Pine arrays when needed
  • ✅ A Flask webhook endpoint that receives TradingView alerts and enriches them with live macro context from FXMacroData
  • ✅ A COT positioning extension that adds speculative sentiment to the picture

Next steps: Extend the webhook handler to post to Telegram or Slack, add a release calendar check so the indicator highlights upcoming events as well as past ones, or combine two currencies (e.g. EUR and USD policy rates) to derive a rate-differential overlay on EUR/USD. Explore the full indicator catalogue at /api-data-docs to find the macro series that best match your strategy.