TickAtlas
Tutorial 12 min read · March 28, 2026

How to Build a Financial Newsletter with Automated Market Data

Create an automated financial newsletter that pulls live indicator data, generates market commentary, and sends to subscribers on a schedule.

CG
By the TickAtlas team

The Newsletter Business Model

Financial newsletters are one of the most profitable content businesses. The key challenge is producing consistent, data-driven content on a schedule. By automating the data collection and initial analysis, you can focus on the editorial -- the insights that subscribers actually pay for.

This tutorial builds a system that: (1) pulls market data from the TickAtlas API, (2) generates a structured market brief, (3) formats it as an email, and (4) sends it via an email service.

Architecture

Cron Job (daily at 6:00 AM UTC)
  |
  v
Python Script:
  1. Fetch indicators for watchlist (TickAtlas API)
  2. Identify notable signals
  3. Generate market summary
  4. Format as HTML email
  5. Send via SendGrid/Mailgun

Step 1: Fetch Market Data

python
import requests
from dataclasses import dataclass, field

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://tickatlas.com/v1"

WATCHLIST = ["EURUSD", "GBPUSD", "USDJPY", "XAUUSD", "BTCUSD"]

@dataclass
class SymbolBrief:
    symbol: str
    close: float
    rsi: float
    macd_signal: str
    trend: str
    notable: bool = False
    note: str = ""

def fetch_symbol_brief(symbol: str) -> SymbolBrief:
    indicators = {}
    ohlcv = {}

    for ind in ["RSI_14", "MACD", "EMA_50"]:
        resp = requests.get(
            f"{BASE_URL}/indicator",
            headers={"X-API-Key": API_KEY},
            params={"symbol": symbol, "indicator": ind, "timeframe": "D1"},
        )
        data = resp.json()["data"]
        indicators[ind] = data["values"]
        indicators[f"{ind}_signal"] = data.get("signal", "neutral")
        if not ohlcv:
            ohlcv = data["ohlcv"]

    close = ohlcv["close"]
    rsi = indicators["RSI_14"]["rsi"]
    ema = indicators["EMA_50"]["value"]
    trend = "bullish" if close > ema else "bearish"

    brief = SymbolBrief(
        symbol=symbol,
        close=close,
        rsi=rsi,
        macd_signal=indicators["MACD_signal"],
        trend=trend,
    )

    # Flag notable conditions
    if rsi > 75 or rsi < 25:
        brief.notable = True
        brief.note = f"RSI at {rsi:.1f} -- {'overbought' if rsi > 75 else 'oversold'}"

    return brief

briefs = [fetch_symbol_brief(sym) for sym in WATCHLIST]

Step 2: Generate the Market Summary

python
from datetime import datetime

def generate_summary(briefs: list[SymbolBrief]) -> str:
    """Generate a plain-text market summary."""
    date = datetime.utcnow().strftime("%B %d, %Y")
    lines = [f"Market Brief -- {date}", "=" * 40, ""]

    # Notable signals first
    notable = [b for b in briefs if b.notable]
    if notable:
        lines.append("NOTABLE SIGNALS:")
        for b in notable:
            lines.append(f"  {b.symbol}: {b.note}")
        lines.append("")

    # Full watchlist
    lines.append("WATCHLIST OVERVIEW:")
    for b in briefs:
        arrow = "^" if b.trend == "bullish" else "v"
        lines.append(
            f"  {b.symbol}: {b.close:.5f} {arrow} | "
            f"RSI: {b.rsi:.1f} | MACD: {b.macd_signal} | "
            f"Trend: {b.trend}"
        )

    return "\n".join(lines)

Step 3: Format as HTML Email

python
def format_html_email(briefs: list[SymbolBrief]) -> str:
    date = datetime.utcnow().strftime("%B %d, %Y")
    rows = ""
    for b in briefs:
        color = "#22c55e" if b.trend == "bullish" else "#ef4444"
        highlight = ' style="background:#fef3c7;"' if b.notable else ""
        rows += f"""
        <tr{highlight}>
          <td style="padding:8px;font-weight:bold;">{b.symbol}</td>
          <td style="padding:8px;">{b.close:.5f}</td>
          <td style="padding:8px;">{b.rsi:.1f}</td>
          <td style="padding:8px;color:{color};">{b.trend}</td>
          <td style="padding:8px;">{b.note or '-'}</td>
        </tr>"""

    return f"""
    <div style="font-family:sans-serif;max-width:600px;margin:0 auto;">
      <h1 style="color:#1a1a2e;">Market Brief</h1>
      <p style="color:#666;">{date}</p>
      <table style="width:100%;border-collapse:collapse;border:1px solid #ddd;">
        <tr style="background:#f8f9fa;">
          <th style="padding:8px;text-align:left;">Symbol</th>
          <th style="padding:8px;">Price</th>
          <th style="padding:8px;">RSI</th>
          <th style="padding:8px;">Trend</th>
          <th style="padding:8px;">Note</th>
        </tr>
        {rows}
      </table>
      <p style="color:#999;font-size:12px;margin-top:20px;">
        Data sourced from TickAtlas API. Not financial advice.
      </p>
    </div>"""

Step 4: Send via Email Service

python
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

def send_newsletter(html_content: str, recipients: list[str]):
    msg = MIMEMultipart("alternative")
    msg["Subject"] = f"Market Brief - {datetime.utcnow().strftime('%b %d')}"
    msg["From"] = "[email protected]"

    msg.attach(MIMEText(html_content, "html"))

    with smtplib.SMTP("smtp.sendgrid.net", 587) as server:
        server.starttls()
        server.login("apikey", "YOUR_SENDGRID_KEY")
        for recipient in recipients:
            msg["To"] = recipient
            server.sendmail(msg["From"], recipient, msg.as_string())

# Full pipeline
briefs = [fetch_symbol_brief(sym) for sym in WATCHLIST]
html = format_html_email(briefs)
send_newsletter(html, ["[email protected]"])

Scaling Ideas

Add AI commentary

Feed the market data to an LLM and generate a paragraph of analysis for each notable signal. See our prompt engineering guide.

Personalized watchlists

Let subscribers choose which symbols they want in their brief. Store preferences in a database and generate per-user content.

Alert-based extras

Send intraday alerts when RSI hits extreme levels. Use the same API calls but trigger on threshold breaches instead of a schedule.

Related Reading

Try this with live data

Every account gets $2.50 in free PAYG credits. No card required — paste your API key and run the code above against live broker data.