← Files DayTrading.MonsterARCHIVED FILE
skills/market-daily-strategist/scripts/narrative_status.py
6.32 KB · Oct 3, 2026 · 06:35 UTC
#!/usr/bin/env python3
"""Fetch and filter the current DTM news-details index.
This helper is intentionally narrow: it is an optional pre-screen for market
reports, not a primary news or price source.
"""
from __future__ import annotations
import argparse
import json
import sys
import urllib.error
import urllib.request
from datetime import datetime, timezone
from typing import Any
DEFAULT_URL = "https://daytrading.monster/api/24hfeed/details"
MARKET_KEYS = {
"us": ["ai", "us_iran", "commodities", "rates_bonds", "spx_gamma", "non_ai", "crypto"],
"jp": ["nikkei", "ai", "commodities", "rates_bonds", "us_iran", "non_ai"],
"cn": ["cn_market", "commodities", "rates_bonds", "us_iran", "ai"],
"global": ["us_iran", "commodities", "rates_bonds", "spx_gamma", "nikkei", "cn_market", "crypto"],
"crypto": ["crypto", "rates_bonds", "spx_gamma"],
}
def parse_time(value: str | None) -> datetime | None:
if not value:
return None
try:
normalized = value.replace("Z", "+00:00")
return datetime.fromisoformat(normalized).astimezone(timezone.utc)
except ValueError:
return None
def age_hours(timestamp: datetime | None, now: datetime) -> float | None:
if timestamp is None:
return None
return (now - timestamp).total_seconds() / 3600
def fetch_json(url: str, timeout: int) -> dict[str, Any]:
request = urllib.request.Request(
url,
headers={"Accept": "application/json", "User-Agent": "codex-market-skills/1.0"},
)
with urllib.request.urlopen(request, timeout=timeout) as response:
payload = response.read()
return json.loads(payload)
def select_keys(market: str) -> list[str]:
if market == "all":
return []
return MARKET_KEYS[market]
def entry_fresh(entry: dict[str, Any], now: datetime, max_entry_age_hours: float) -> tuple[bool, float | None]:
primary_time = parse_time(entry.get("time"))
entry_age = age_hours(primary_time, now)
if entry_age is None:
return False, None
return 0 <= entry_age <= max_entry_age_hours, entry_age
def build_summary(data: dict[str, Any], args: argparse.Namespace) -> dict[str, Any]:
if data.get("ok") is not True or not isinstance(data.get("items"), list):
raise ValueError("details API did not return ok=true and items[]")
now = datetime.now(timezone.utc)
generated_at = parse_time(data.get("generated_at"))
reviewed_at = parse_time(data.get("reviewed_at"))
feed_age = age_hours(generated_at or reviewed_at, now)
feed_is_fresh = feed_age is not None and 0 <= feed_age <= args.max_feed_age_hours
wanted_keys = set(select_keys(args.market))
groups: dict[str, dict[str, Any]] = {}
for entry in data["items"]:
key = entry.get("category")
if wanted_keys and key not in wanted_keys:
continue
group = groups.setdefault(key, {"key": key, "label": entry.get("category_label"),
"freshEntries": [], "staleEntriesSkipped": 0})
is_fresh, entry_age = entry_fresh(entry, now, args.max_entry_age_hours)
if not is_fresh:
group["staleEntriesSkipped"] += 1
elif len(group["freshEntries"]) < args.limit:
group["freshEntries"].append({"title": entry.get("title"), "url": entry.get("url"),
"time": entry.get("time"), "ageHours": round(entry_age, 2)})
return {
"status": "ok" if feed_is_fresh else "stale_feed",
"generatedAt": data.get("generated_at"),
"reviewedAt": data.get("reviewed_at"),
"feedAgeHours": None if feed_age is None else round(feed_age, 2),
"maxFeedAgeHours": args.max_feed_age_hours,
"maxEntryAgeHours": args.max_entry_age_hours,
"market": args.market,
"items": list(groups.values()),
"discipline": "Use as a narrative pre-screen only; verify facts, prices, and market reaction elsewhere.",
}
def render_markdown(summary: dict[str, Any]) -> str:
lines = [
f"# News Details Status ({summary['market']})",
"",
f"- status: {summary['status']}",
f"- generatedAt: {summary.get('generatedAt')}",
f"- reviewedAt: {summary.get('reviewedAt')}",
f"- feedAgeHours: {summary.get('feedAgeHours')} / max {summary.get('maxFeedAgeHours')}",
f"- entryFreshnessHours: max {summary.get('maxEntryAgeHours')}",
"- use: narrative pre-screen only; verify facts, prices, and market reaction elsewhere",
"",
]
for item in summary["items"]:
entries = item["freshEntries"]
if not entries:
continue
lines.append(f"## {item['key']} - {item.get('label')}")
for entry in entries:
lines.append(
f"- [{entry.get('title')}]({entry.get('url')}) "
f"(time={entry.get('time')}, ageHours={entry.get('ageHours')})"
)
if item["staleEntriesSkipped"]:
lines.append(f"- skipped stale entries: {item['staleEntriesSkipped']}")
lines.append("")
return "\n".join(lines).rstrip() + "\n"
def parse_args(argv: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--url", default=DEFAULT_URL)
parser.add_argument("--market", choices=["us", "jp", "cn", "global", "crypto", "all"], default="global")
parser.add_argument("--limit", type=int, default=5, help="Fresh news entries per category")
parser.add_argument("--max-feed-age-hours", type=float, default=6.0)
parser.add_argument("--max-entry-age-hours", type=float, default=24.0)
parser.add_argument("--timeout", type=int, default=10)
parser.add_argument("--format", choices=["markdown", "json"], default="markdown")
return parser.parse_args(argv)
def main(argv: list[str]) -> int:
args = parse_args(argv)
try:
data = fetch_json(args.url, args.timeout)
summary = build_summary(data, args)
except (urllib.error.URLError, TimeoutError, ValueError, OSError) as exc:
print(json.dumps({"status": "error", "error": str(exc)}, ensure_ascii=False, indent=2))
return 2
if args.format == "json":
print(json.dumps(summary, ensure_ascii=False, indent=2))
else:
print(render_markdown(summary))
return 0 if summary["status"] == "ok" else 1
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))
SHA-256: 122065b935895722928cdf9c1261597564aad40dc75bfb378b8f4aec42a0332e