BTC / USD
$67,842
+2.14%
ETH / USD
$3,521
+1.87%
S&P 500
5,312
+0.43%
US 10Y Yield
4.38%
−3 bps
DXY Index
104.21
−0.18%
Gold (XAU)
$2,348
+0.62%
Sentiment Index
68
Greed
Macro Surprise
+0.4σ
Above trend
Cross-asset momentum: positive
BTC trend: bullish
Vol regime: elevated
Yield curve: steepening
Geopolitical risk: high
Last run: 2026-04-28 06:00 UTC · MuleRun Computer · cron 0 6 * * 1-5 · 14 API calls · 2.3 s total

Scenario Overview

A treasury and trading ops team uses MuleRun Computer to run an automated morning brief every weekday at 06:00 UTC. The job queries 10 MuleRun market-data API categories, normalizes data into a unified schema, computes composite signal scores, archives all outputs to MuleRun Drive, and publishes a static MuleRun Page dashboard — the page you are reading now.

The entire pipeline — from cron trigger to published page — runs unattended on a persistent MuleRun Computer instance. No manual intervention, no external CI, no cloud-function glue. One VM, one script, one schedule.

Stack: MuleRun Computer (Ubuntu VM) · MuleRun market-data APIs · MuleRun Drive (file archive) · MuleRun Pages (static hosting) · Python 3.12 + requests + pandas · Cron scheduler
CRON 0 6 * * 1-5 10 API CATEGORIES PYTHON 3.12 JSON + CSV

Workflow Pipeline

Seven stages execute sequentially inside a single Python process on MuleRun Computer.

Step 1
Schedule Trigger
Cron fires at 06:00 UTC Mon–Fri. MuleRun Computer executes run_morning_brief.py.
Step 2
Market-Data API Calls
10 parallel requests to MuleRun market-data endpoints. Retry on 429/5xx with exponential backoff.
Step 3
Normalization
Raw JSON merged into a unified schema. Timestamps aligned to UTC midnight. Missing fields filled with last-known values.
Step 4
Signal Scoring
Composite Z-scores across momentum, sentiment, macro surprise, and vol regime. Thresholds: green <1σ, amber 1–2σ, red >2σ.
Step 5
Drive Archive
JSON + CSV written to /market-signal-ops/YYYY-MM-DD/ on MuleRun Drive. Immutable daily snapshots.
Step 6
Alert Summary
If any signal crosses red threshold, push alert via webhook. Summary appended to run-log.json.
Step 7
Page Publish
dashboard-data.json injected into static HTML template. Published to MuleRun Pages via CLI.

Market-Data API Categories

The pipeline queries all 10 categories from the MuleRun market-data MCP server. Endpoint examples shown below are illustrative.

#CategoryMCP ToolEndpoint ExampleUsage in Pipeline
1Crypto Pricecrypto_pricee.g. crypto_price({symbol:"BTC"})BTC, ETH spot + 24h change for dashboard cards
2Crypto Marketcrypto_markete.g. crypto_market({metric:"total_mcap"})Total market cap, dominance, volume for breadth signals
3Technical Analysistechnical_analysise.g. technical_analysis({symbol:"BTC",indicators:["RSI","MACD"]})RSI, MACD, Bollinger Bands for momentum score
4Sentiment Indexsentiment_indexe.g. sentiment_index({source:"aggregate"})Fear & Greed composite for sentiment Z-score
5News Feednews_feede.g. news_feed({category:"crypto",limit:20})Top 20 crypto headlines for narrative tagging
6Macro Indicatorsmacro_indicatorse.g. macro_indicators({series:["CPI","NFP","PMI"]})CPI, NFP, PMI surprises for macro composite
7Rates & Yieldsrates_yieldse.g. rates_yields({curves:["US2Y","US10Y","US30Y"]})Yield curve shape, real rates for risk-free signal
8Global Assetsglobal_assetse.g. global_assets({assets:["SPX","DXY","XAU"]})S&P 500, DXY, Gold for cross-asset dashboard
9Cross Assetcross_assete.g. cross_asset({analysis:"correlation_matrix"})BTC vs SPX/Gold/DXY rolling correlation for regime detection
10TradFi Newstradfi_newse.g. tradfi_news({topics:["fed","earnings"],limit:10})Fed & earnings headlines for macro narrative overlay
All endpoint examples above are illustrative. Actual tool parameters may vary. Consult MuleRun market-data MCP documentation for current schemas.

MuleRun Drive — Archive Structure

Each daily run writes six files to an immutable date-partitioned folder. Files are never overwritten; re-runs append a suffix.

/market-signal-ops/
  2026-04-28/
    prices.json 48 KB
    macro.json 12 KB
    signals.json 8 KB
    news.json 34 KB
    run-log.json 3 KB
    dashboard-data.json 22 KB
  2026-04-25/
    … same structure …

File Descriptions

prices.jsonSpot prices, 24h changes, volumes for BTC, ETH, and global assets.
macro.jsonMacro indicator values, surprises, yield curve snapshot.
signals.jsonComposite Z-scores, signal classifications, threshold breaches.
news.jsonTop headlines from crypto and tradfi feeds with sentiment tags.
run-log.jsonExecution metadata: timings, retries, errors, API response codes.
dashboard-data.jsonPre-computed payload injected into the static Page template.

Computer Job — Code Snippets

Shell: Cron & Entry Point

bash
# /etc/cron.d/market-signal-ops
0 6 * * 1-5  mulerun  /opt/market-signal-ops/run.sh

# run.sh — entry point on MuleRun Computer
#!/usr/bin/env bash
set -euo pipefail
cd /opt/market-signal-ops
source .venv/bin/activate

export RUN_DATE=$(date -u +%Y-%m-%d)
export DRIVE_PATH="/market-signal-ops/${RUN_DATE}"

python run_morning_brief.py \
  --date "$RUN_DATE" \
  --drive-path "$DRIVE_PATH" \
  --publish 2>&1 | tee "logs/${RUN_DATE}.log"

# Upload log to Drive
mulerun drive upload \
  "logs/${RUN_DATE}.log" \
  "${DRIVE_PATH}/run-log.txt"

Python: API Calls & Signal Scoring

python
import asyncio, json, httpx
from datetime import datetime, timezone

API_CATEGORIES = [
    "crypto_price", "crypto_market",
    "technical_analysis", "sentiment_index",
    "news_feed", "macro_indicators",
    "rates_yields", "global_assets",
    "cross_asset", "tradfi_news",
]

async def fetch_all(session):
    """Parallel fetch with retry."""
    tasks = [call_api(session, cat)
             for cat in API_CATEGORIES]
    return await asyncio.gather(
        *tasks, return_exceptions=True
    )

async def call_api(session, category,
                    retries=3, backoff=1.0):
    for attempt in range(retries):
        try:
            r = await session.post(
                MULERUN_MCP_ENDPOINT,
                json={"tool": category,
                       "params": PARAMS[category]},
                timeout=10,
            )
            r.raise_for_status()
            data = r.json()
            if is_stale(data):
                raise StaleDataError(category)
            return data
        except (httpx.HTTPStatusError,
                httpx.TimeoutException) as e:
            if attempt == retries - 1:
                raise
            await asyncio.sleep(
                backoff * (2 ** attempt))

def compute_signals(raw: dict) -> dict:
    """Composite Z-score across dimensions."""
    z = {}
    z["momentum"] = zscore(
        raw["technical_analysis"]["rsi"], 50, 14)
    z["sentiment"] = zscore(
        raw["sentiment_index"]["value"], 50, 25)
    z["macro"] = zscore(
        raw["macro_indicators"]["surprise"],
        0, 0.8)
    z["vol"] = zscore(
        raw["cross_asset"]["realised_vol"],
        18, 6)
    z["composite"] = sum(z.values()) / len(z)
    return z

Python: Drive Upload & Page Publish

python
import subprocess, json, os

def archive_to_drive(data: dict, drive_path: str):
    """Write all output files to MuleRun Drive."""
    files = {
        "prices.json":  data["prices"],
        "macro.json":   data["macro"],
        "signals.json": data["signals"],
        "news.json":    data["news"],
        "dashboard-data.json": data["dashboard"],
    }
    for name, content in files.items():
        local = f"/tmp/{name}"
        with open(local, "w") as f:
            json.dump(content, f, indent=2)
        subprocess.run([
            "mulerun", "drive", "upload",
            local, f"{drive_path}/{name}"
        ], check=True)

def publish_page(dashboard_data: dict):
    """Inject data into template and publish."""
    with open("template.html") as f:
        html = f.read()
    html = html.replace(
        "/*__DATA__*/",
        json.dumps(dashboard_data))
    with open("/tmp/index.html", "w") as f:
        f.write(html)
    subprocess.run([
        "mulerun", "page", "deploy",
        "--name", "market-signal-ops",
        "--file", "/tmp/index.html"
    ], check=True)

Sample Signal Table

Excerpt from signals.json — composite signal decomposition for 2026-04-28.

DimensionRaw ValueMeanStd DevZ-ScoreSignal
Momentum (RSI-14)62.450.014.0+0.89 Green
Sentiment685025+0.72 Green
Macro Surprise+0.320.000.80+0.40 Green
Vol Regime24.1%18.0%6.0%+1.02 Amber
Yield Curve Δ−3 bps0 bps5 bps−0.60 Green
BTC-SPX Correlation0.410.350.15+0.40 Green
Composite+0.47 Green

Visual Analytics (CSS / SVG)

7-Day Composite Signal

+1σ −1σ Apr 21 Apr 23 Apr 25 Apr 27 Apr 28

Signal Decomposition — Z-Scores

Momentum
Sentiment
Macro
Vol
Yield
Correl.

Cross-Asset 24h Change

0% +2.14%BTC +1.87%ETH +0.43%SPX +0.62%XAU −0.18%DXY −3 bpsUS10Y

Sentiment Index — 30-Day Trend

50 80 20 68

Resilience: Retry, Rate-Limit & Stale-Data Handling

Retry Strategy

Exponential backoff: 1 s → 2 s → 4 s. Max 3 attempts per endpoint. On final failure, the category is marked degraded in run-log.json and excluded from the composite score. The dashboard renders a caution badge for any degraded category.

Rate-Limit Handling

HTTP 429 responses are caught by the retry loop. The script reads the Retry-After header and waits accordingly before the next attempt. If rate limits are consistently hit, the cron schedule can be shifted by 5-minute jitter: */5 offset.

Stale-Data Detection

Each API response includes a timestamp field. If the data is older than 4 hours, it is flagged as stale. Stale data is still archived but excluded from signal scoring. The dashboard shows an amber "STALE" badge on the affected card.

python
def is_stale(data: dict, max_age_hours: int = 4) -> bool:
    ts = datetime.fromisoformat(data["timestamp"])
    age = datetime.now(timezone.utc) - ts
    return age.total_seconds() > max_age_hours * 3600

def classify_response(resp, category):
    """Classify API response for run-log."""
    if resp is None:
        return {"status": "failed", "category": category}
    if isinstance(resp, Exception):
        return {"status": "error", "category": category,
                "error": str(resp)}
    if is_stale(resp):
        return {"status": "stale", "category": category}
    return {"status": "ok", "category": category}

Security Notes

Credential Management
API keys are stored in MuleRun Computer's encrypted environment variables — never in source files or Drive. The MULERUN_API_KEY env var is set via mulerun computer env set and injected at runtime. Rotation is handled through MuleRun's key management console.
Network & Access Control
The Computer instance runs in an isolated network segment. Outbound traffic is restricted to MuleRun API endpoints and MuleRun Drive/Pages services via allowlist. SSH access requires MuleRun identity verification. All Drive writes are append-only; deletion requires elevated permissions.
Audit Trail
Every run produces a run-log.json archived to Drive. Logs include: API response codes, latencies, retry counts, data freshness checks, signal threshold breaches, and publish status. Logs are immutable once written.
Page Security
The published Page is a static HTML file with no server-side logic. No user input is accepted. All data is pre-rendered at build time. The page contains no tracking scripts, no external requests, and no cookies.

Outcomes

2.3 s
End-to-end pipeline execution time
14
API calls per run across 10 categories
99.6%
Uptime over 90 days of weekday runs
0
Manual interventions required
6
Files archived per run to MuleRun Drive
<1 min
From data collection to live published dashboard
Summary: By combining MuleRun Computer (persistent VM + cron), MuleRun market-data APIs (10 categories, single MCP interface), MuleRun Drive (immutable archive), and MuleRun Pages (static hosting), the trading ops team eliminated all manual morning-brief work. The risk dashboard updates itself every weekday before markets open, with full audit trail and zero external dependencies.