OHLC.dev editorialeconomic calender

Build an Economic Calendar Dashboard Using Python and RapidAPI

This project demonstrates how to build an Economic Calendar Dashboard using Python and the Economic Calendar API on RapidAPI. The notebook retrieves today's and tomorrow's economic calendar events, normalizes multiple JSON response formats, converts them into structured pandas DataFrames, and displays an easy-to-read dashboard summarizing upcoming economic events from around the world

July 23, 20268 min readRafatar
Build an Economic Calendar Dashboard Using Python and RapidAPI

Economic events often become the main drivers of market volatility. Interest rate decisions, inflation reports, employment statistics, and GDP releases can significantly influence currencies, commodities, stock markets, and cryptocurrencies.

Monitoring these events manually every day can be time-consuming. Fortunately, the Economic Calendar API available on RapidAPI makes it possible to automate this process using Python.

In this tutorial, we'll build an Economic Calendar Dashboard that retrieves today's and tomorrow's economic events, processes multiple API response formats automatically, and presents the information in a clean, readable dashboard.

The notebook has been divided into five logical sections that cover API configuration, secure request handling, data retrieval, response normalization, and dashboard generation. Throughout the project, several validation mechanisms are included to ensure the application continues running even when the API returns empty responses or unexpected JSON structures.

CELL 1 — Import Library and API Configuration

# ============================================================
# CELL 1 — IMPORT LIBRARY DAN KONFIGURASI API
# ============================================================

import requests
import pandas as pd
import json
import time
from datetime import datetime

# Masukkan RapidAPI Key Anda
API_KEY = "MASUKKAN_RAPIDAPI_KEY_ANDA"

BASE_URL = "https://economic-calendar-api.p.rapidapi.com"

HEADERS = {
    "Content-Type": "application/json",
    "x-rapidapi-key": API_KEY,
    "x-rapidapi-host": "economic-calendar-api.p.rapidapi.com"
}

print("=" * 80)
print("ECONOMIC CALENDAR API")
print("=" * 80)
print("Konfigurasi berhasil dibuat.")
print("Base URL :", BASE_URL)

The first cell prepares the entire environment for the project.

It imports the required libraries including requests, pandas, json, time, and datetime, before configuring the Economic Calendar API endpoint hosted on RapidAPI.

Instead of hardcoding API settings throughout the notebook, the project stores the API key, base URL, and HTTP headers in reusable variables. This design makes future maintenance much easier while keeping every API request consistent.

Finally, the notebook prints a simple confirmation showing that the API configuration has been created successfully.

CELL 2 — Building a Safe API Request Function

# ============================================================
# CELL 2 — FUNGSI REQUEST API
# ============================================================

def request_api(endpoint, params=None, timeout=40, max_retry=2):
    """
    Mengambil data dari Economic Calendar API dengan:
    - Timeout handling
    - Retry sederhana
    - Pemeriksaan status HTTP
    - Penanganan respons kosong
    """

    url = f"{BASE_URL}{endpoint}"

    for attempt in range(1, max_retry + 1):
        try:
            response = requests.get(
                url,
                headers=HEADERS,
                params=params,
                timeout=timeout
            )

            print("-" * 80)
            print("Endpoint :", endpoint)
            print("URL      :", response.url)
            print("Status   :", response.status_code)

            if response.status_code == 200:
                try:
                    result = response.json()

                    if result is None:
                        print("⚠️ Respons API bernilai null.")
                        return None

                    print("✅ Data berhasil diambil.")
                    return result

                except ValueError:
                    print("❌ Respons bukan JSON yang valid.")
                    print("Respons :", response.text[:500])
                    return None

            elif response.status_code == 401:
                print("❌ API Key tidak valid atau belum dimasukkan.")
                return None

            elif response.status_code == 403:
                print("❌ Akses ditolak. Pastikan sudah berlangganan API.")
                return None

            elif response.status_code == 429:
                print("⚠️ Rate limit tercapai.")

                if attempt < max_retry:
                    print("Mencoba kembali...")
                    time.sleep(3)
                    continue

                return None

            else:
                print("❌ Request gagal.")
                print("Respons :", response.text[:500])
                return None

        except requests.exceptions.Timeout:
            print("-" * 80)
            print("Endpoint :", endpoint)
            print(f"❌ Request timeout pada percobaan {attempt}.")

            if attempt < max_retry:
                print("Mencoba kembali...")
                time.sleep(2)

        except requests.exceptions.ConnectionError:
            print("❌ Tidak dapat terhubung ke server API.")
            return None

        except requests.exceptions.RequestException as error:
            print("❌ Request error:", error)
            return None

    return None

Rather than calling the API directly multiple times, the notebook introduces a reusable request_api() function.

This function performs several important tasks automatically:

  • Creates the request URL.

  • Sends authenticated HTTP requests.

  • Handles timeout exceptions.

  • Retries failed requests caused by temporary issues.

  • Detects invalid API keys.

  • Handles HTTP 403 and HTTP 429 responses.

  • Validates whether the API returns valid JSON.

  • Prevents the notebook from stopping unexpectedly when errors occur.

By centralizing all communication inside one function, every endpoint benefits from the same robust error-handling logic while keeping the remaining notebook much cleaner.

CELL 3 — Retrieving Today's and Tomorrow's Economic Calendar

# ============================================================
# CELL 3 — REQUEST DATA TODAY DAN TOMORROW
# ============================================================

today_params = {
    "timezone": "GMT+0",
    "volatility": "NONE",
    "limit": 50
}

tomorrow_params = {
    "timezone": "GMT+0",
    "volatility": "NONE",
    "limit": 50
}

today_response = request_api(
    endpoint="/calendar/history/today",
    params=today_params
)

tomorrow_response = request_api(
    endpoint="/calendar/history/tomorrow",
    params=tomorrow_params
)

print("\n" + "=" * 80)
print("HASIL REQUEST")
print("=" * 80)
print("Today Response    :", type(today_response).__name__)
print("Tomorrow Response :", type(tomorrow_response).__name__)

Once the reusable request function has been prepared, the notebook retrieves two separate datasets from the Economic Calendar API.

The first request downloads all available economic events scheduled for today, while the second request retrieves the events planned for tomorrow. Both requests use the same parameters, including:

  • GMT+0 timezone

  • NONE volatility filter

  • Maximum of 50 records

After each request completes, the notebook prints the response type, making it easier to verify whether the API successfully returned valid JSON before continuing to the normalization stage.

CELL 4 — Normalizing Different API Response Structures

# ============================================================
# CELL 4 — NORMALISASI DAN PEMBERSIHAN DATA
# ============================================================

def extract_records(response):
    """
    Mencari list data dari berbagai kemungkinan struktur JSON.
    """

    if response is None:
        return []

    if isinstance(response, list):
        return response

    if not isinstance(response, dict):
        return []

    possible_keys = [
        "data",
        "results",
        "result",
        "events",
        "calendar",
        "items",
        "records",
        "response"
    ]

    for key in possible_keys:
        value = response.get(key)

        if isinstance(value, list):
            return value

        if isinstance(value, dict):
            for nested_key in possible_keys:
                nested_value = value.get(nested_key)

                if isinstance(nested_value, list):
                    return nested_value

    # Jika dictionary langsung berisi satu event
    event_keys = [
        "event",
        "title",
        "country",
        "currency",
        "date",
        "time"
    ]

    if any(key in response for key in event_keys):
        return [response]

    return []


def get_first_value(item, keys, default="-"):
    """
    Mengambil nilai pertama yang tersedia dari beberapa nama key.
    """

    if not isinstance(item, dict):
        return default

    for key in keys:
        value = item.get(key)

        if value not in [None, "", [], {}]:
            return value

    return default


def normalize_calendar(response, period):
    records = extract_records(response)
    normalized = []

    for item in records:
        if not isinstance(item, dict):
            continue

        normalized.append({
            "Periode": period,
            "Tanggal": get_first_value(
                item,
                ["date", "event_date", "datetime", "timestamp", "start"]
            ),
            "Waktu": get_first_value(
                item,
                ["time", "event_time", "hour"]
            ),
            "Negara": get_first_value(
                item,
                ["country", "country_name", "nation"]
            ),
            "Mata Uang": get_first_value(
                item,
                ["currency", "currency_code", "symbol"]
            ),
            "Agenda": get_first_value(
                item,
                ["event", "title", "name", "indicator", "description"]
            ),
            "Volatilitas": get_first_value(
                item,
                ["volatility", "importance", "impact", "priority"]
            ),
            "Aktual": get_first_value(
                item,
                ["actual", "actual_value", "value"]
            ),
            "Perkiraan": get_first_value(
                item,
                ["forecast", "forecast_value", "consensus"]
            ),
            "Sebelumnya": get_first_value(
                item,
                ["previous", "previous_value", "prior"]
            )
        })

    return pd.DataFrame(normalized)


df_today = normalize_calendar(today_response, "Hari Ini")
df_tomorrow = normalize_calendar(tomorrow_response, "Besok")

df_calendar = pd.concat(
    [df_today, df_tomorrow],
    ignore_index=True
)

print("=" * 100)
print("HASIL NORMALISASI DATA")
print("=" * 100)
print("Jumlah agenda hari ini :", len(df_today))
print("Jumlah agenda besok    :", len(df_tomorrow))
print("Total agenda           :", len(df_calendar))

if not df_calendar.empty:
    display(df_calendar.head(10))
else:
    print("\n⚠️ Tidak ada data kalender ekonomi yang dapat dinormalisasi.")

    print("\nDEBUG RESPONS TODAY:")
    print(json.dumps(today_response, indent=2, ensure_ascii=False)[:3000])

    print("\nDEBUG RESPONS TOMORROW:")
    print(json.dumps(tomorrow_response, indent=2, ensure_ascii=False)[:3000])

Different APIs frequently return JSON using different structures. Sometimes the event list appears under data, while other responses may use keys such as results, calendar, items, or records.

To solve this challenge, Cell 4 introduces several helper functions that automatically discover where the actual event list is stored.

The notebook extracts today's and tomorrow's events independently before converting them into standardized DataFrames containing fields such as:

  • Period

  • Date

  • Time

  • Country

  • Currency

  • Event

  • Volatility

  • Actual Value

  • Forecast

  • Previous Value

Finally, both DataFrames are combined into one consolidated economic calendar dataset that can be used throughout the remainder of the project.

CELL 5 — Building the Economic Calendar Dashboard

# ============================================================
# CELL 5 — DASHBOARD KALENDER EKONOMI
# ============================================================

def safe_text(value):
    if value is None:
        return "-"

    text = str(value).strip()

    if text.lower() in ["", "none", "nan", "null"]:
        return "-"

    return text


def print_calendar_section(dataframe, title, icon):
    print(f"\n{icon} {title}")
    print("-" * 100)

    if dataframe.empty:
        print("Tidak ada agenda ekonomi.")
        return

    print(f"Jumlah Agenda : {len(dataframe)}\n")

    for index, row in dataframe.head(15).iterrows():
        number = index + 1

        date = safe_text(row.get("Tanggal"))
        time_value = safe_text(row.get("Waktu"))
        country = safe_text(row.get("Negara"))
        currency = safe_text(row.get("Mata Uang"))
        event = safe_text(row.get("Agenda"))
        volatility = safe_text(row.get("Volatilitas"))
        actual = safe_text(row.get("Aktual"))
        forecast = safe_text(row.get("Perkiraan"))
        previous = safe_text(row.get("Sebelumnya"))

        print(f"{number:02d}. {event}")
        print(f"    Tanggal      : {date}")
        print(f"    Waktu        : {time_value} GMT+0")
        print(f"    Negara       : {country}")
        print(f"    Mata Uang    : {currency}")
        print(f"    Volatilitas  : {volatility}")
        print(f"    Aktual       : {actual}")
        print(f"    Perkiraan    : {forecast}")
        print(f"    Sebelumnya   : {previous}")
        print("-" * 100)

    if len(dataframe) > 15:
        print(f"Menampilkan 15 dari {len(dataframe)} agenda.")


print("=" * 100)
print("ECONOMIC CALENDAR DASHBOARD")
print("=" * 100)

print_calendar_section(
    df_today.reset_index(drop=True),
    "KALENDER EKONOMI HARI INI",
    "📅"
)

print_calendar_section(
    df_tomorrow.reset_index(drop=True),
    "KALENDER EKONOMI BESOK",
    "🔮"
)

print("\n" + "=" * 100)
print("RINGKASAN")
print("=" * 100)

total_today = len(df_today)
total_tomorrow = len(df_tomorrow)
total_all = len(df_calendar)

print(f"📅 Agenda Hari Ini : {total_today}")
print(f"🔮 Agenda Besok    : {total_tomorrow}")
print(f"📊 Total Agenda    : {total_all}")

if not df_calendar.empty:
    currencies = (
        df_calendar["Mata Uang"]
        .replace("-", pd.NA)
        .dropna()
        .astype(str)
        .unique()
        .tolist()
    )

    countries = (
        df_calendar["Negara"]
        .replace("-", pd.NA)
        .dropna()
        .astype(str)
        .unique()
        .tolist()
    )

    print(
        "💱 Mata Uang       :",
        ", ".join(currencies[:10]) if currencies else "-"
    )

    print(
        "🌍 Negara          :",
        ", ".join(countries[:10]) if countries else "-"
    )

    print("✅ Status           : Data kalender berhasil diproses")
else:
    print("⚠️ Status           : Tidak ada data atau request API gagal")

print("=" * 100)

The final cell transforms the normalized DataFrames into an organized dashboard.

Today's events and tomorrow's events are displayed separately, making them much easier to read. Each event includes:

  • Date

  • Time

  • Country

  • Currency

  • Event Name

  • Volatility Level

  • Actual Value

  • Forecast

  • Previous Value

To improve readability, the notebook safely replaces empty or missing values with hyphens before displaying them.

The dashboard concludes with a summary showing:

  • Number of today's events

  • Number of tomorrow's events

  • Total events processed

  • Currencies involved

  • Countries represented

  • Overall processing status

This provides a concise overview of the global economic calendar in a single execution.

Overall Workflow

  1. Configure the Economic Calendar API connection.

  2. Create a reusable request function with built-in error handling.

  3. Retrieve today's and tomorrow's economic events.

  4. Normalize different JSON response formats into standardized DataFrames.

  5. Display a complete Economic Calendar Dashboard with a consolidated summary.

Conclusion

The Economic Calendar Dashboard demonstrates how Python can automate one of the most important daily tasks for traders and investors.

Instead of manually checking multiple financial websites, the notebook collects today's and tomorrow's economic events directly from the API, standardizes inconsistent JSON responses, and presents the information in a clean dashboard.

Because the project includes reusable request functions, automatic normalization, comprehensive error handling, and structured reporting, it provides a practical foundation for building more advanced financial monitoring systems such as trading dashboards, automated market alerts, or macroeconomic analysis tools.