add Eurostat + UK ONS sources; valuation/bubble/economy/bonds groups; aggregate read; market-open header
Three new data sources hooked into the existing SOURCES registry. All
open APIs, no keys:
- EUROSTAT: prefix EUROSTAT:dataset?dim=val&... — current EU bond
yields (Bund/OAT/BTP/EZ) and Eurozone economic indicators that
FRED's OECD-mirror series stopped updating in 2022-2023.
- ONS: prefix ONS:topic/cdid/dataset — current UK CPI, unemployment,
GDP, industrial production. Replaces the 5+ month-stale FRED
LRHUTTTTGBM156S mirror.
New indicator groups in default.toml feed the strategic/fundamental
lens we converged on: valuation (CAPE/Buffett anchors), bubble_watch
(SKEW/VVIX/RSP vs SPY/HYG vs TLT/IPO/crypto), economy (multi-region,
ALL current-or-stale-flagged), bonds (UK/EU/US/JPN sovereign yields).
Indicator panel now opens with an AI "read" interpretation per group
(generated hourly at :07 UTC alongside an aggregate cross-group read
shown in the dashboard header). The aggregate is grounded by a markets
strip — NYSE/LSE/Frankfurt/Tokyo/HK/Shanghai with open/closed LEDs and
next-open countdown, computed locally from each exchange's tz.
Other UX bits: indicator-row tooltips populated from TOML notes;
rows whose last observation is >90 days old get a 'stale' chip;
ghost symbols (in DB but no longer in TOML) filtered out of the
panel; Eurostat/ONS symbols display as short codes rather than the
full API path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a10409c02b
commit
1edf9cad41
15 changed files with 1156 additions and 10 deletions
|
|
@ -20,7 +20,7 @@ OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
|
|||
# Bump when the composed prompt changes meaningfully. Stored on every
|
||||
# StrategicLog row so historical logs can be linked to the prompt that produced
|
||||
# them.
|
||||
PROMPT_VERSION = 3
|
||||
PROMPT_VERSION = 4
|
||||
|
||||
|
||||
# --- Core: invariant across tone/analysis settings ----------------------------
|
||||
|
|
@ -60,6 +60,28 @@ numbers in every paragraph. No section over ~150 words.
|
|||
- End with a watch list: 3-5 specific items to track in the next week, \
|
||||
each one sentence.
|
||||
|
||||
# Time-horizon discipline
|
||||
- This is a STRATEGIC log, not a day-trader's read. Treat 1-day moves under \
|
||||
2% as background noise; mention them only when they break or confirm a \
|
||||
multi-week trend or are extreme outliers.
|
||||
- Anchor every claim to multi-week (1m), multi-month (since-anchor), or \
|
||||
multi-year (1y) changes — not 1d. If the only thing happening is a 1d move, \
|
||||
omit the paragraph.
|
||||
- The watch list is for "structural tripwires over the next 1-3 months", not \
|
||||
"things to watch tomorrow". Each watch item should name a level/threshold \
|
||||
whose breach would change the regime, not a calendar-date event.
|
||||
|
||||
# Rational vs irrational framing
|
||||
The reader's primary goal is to disconnect rational decisions from market \
|
||||
irrationality. In every sector or theme paragraph, separately identify:
|
||||
- The RATIONAL drivers: earnings, real-economy data, monetary policy, \
|
||||
structural geopolitical shifts, valuation vs fundamentals.
|
||||
- The IRRATIONAL drivers: positioning, narrative momentum, sentiment \
|
||||
extremes, concentration, flow-driven moves, options gamma, credit complacency.
|
||||
When the two diverge — price moving on irrational drivers while fundamentals \
|
||||
say otherwise, or vice versa — flag the divergence explicitly. Those gaps \
|
||||
are where the next regime change starts.
|
||||
|
||||
# Discipline
|
||||
- No emojis, no marketing language, no "concerning" or "unprecedented" \
|
||||
without a specific number behind it.
|
||||
|
|
@ -68,7 +90,16 @@ without a specific number behind it.
|
|||
predicted X and X did not happen". Both are useful; conflating them is not.
|
||||
- Don't repeat the same point in different words across paragraphs.
|
||||
- No buy/sell recommendations. Triggers are pre-set elsewhere; your job is \
|
||||
to report whether reality is confirming, modifying, or refuting the thesis."""
|
||||
to report whether reality is confirming, modifying, or refuting the thesis.
|
||||
|
||||
# System temperature (closing line, mandatory)
|
||||
Close the log with a single sentence on a line of its own, formatted exactly:
|
||||
|
||||
System temperature: [cool|neutral|elevated|hot|extreme] — [one clause naming the 2-3 specific divergences or readings that justify the label]
|
||||
|
||||
This is the line a reader who only sees the watch list scrolls down to. Make \
|
||||
it earn its place: cite real signals (HY OAS, breadth, VIX, valuation, real \
|
||||
yields), not vibes."""
|
||||
|
||||
|
||||
# --- Tone: audience-shaping block --------------------------------------------
|
||||
|
|
@ -141,6 +172,118 @@ question via the chat sidebar.
|
|||
- Keep the same audience and analysis discipline established above."""
|
||||
|
||||
|
||||
def build_summary_system_prompt(tone: str, analysis: str) -> str:
|
||||
"""A lean, focused system prompt for the per-indicator-group hourly
|
||||
summary. INTERPRETATION not description — the reader has the table
|
||||
next to this paragraph; they don't need numbers recited at them."""
|
||||
tone_block = _TONE.get(tone.upper(), _TONE["INTERMEDIATE"])
|
||||
analysis_block = _ANALYSIS.get(analysis.upper(), _ANALYSIS["SPECULATIVE"])
|
||||
return f"""You write a TINY interpretation (≤60 words, 2-3 sentences) \
|
||||
of ONE indicator group for a strategic markets dashboard.
|
||||
|
||||
# What this is for
|
||||
The reader is looking at the table of numbers right next to your text. \
|
||||
They can see the values. They CANNOT see the meaning. Your job is to \
|
||||
**explain what the data means**, not to recite it. Each sentence should be \
|
||||
a regime-level interpretation, a fundamental driver identification, or a \
|
||||
cross-indicator implication — not a description of moves.
|
||||
|
||||
# Hard constraints
|
||||
- Plain prose, ONE paragraph. No markdown, no headers, no lists, no labels.
|
||||
- Open IMMEDIATELY with substance. NEVER start with: "I need to", "I'll", \
|
||||
"We need to", "We are asked", "Here's", "Let me", "Let's", "Sure", "Looking \
|
||||
at", "Based on", "Summary:", "The data shows", "First", "To address". No \
|
||||
meta-commentary at all.
|
||||
- Cite at most 2-3 specific numbers and ONLY when they anchor an \
|
||||
interpretation. Don't list moves; explain them.
|
||||
- Multi-week / multi-month horizon. 1-day moves under 2% are noise — skip.
|
||||
- No buy/sell language. No predictions. No watch list. No TL;DR. No date \
|
||||
header. No "system temperature" line — that belongs to the full daily log.
|
||||
|
||||
{tone_block}
|
||||
|
||||
{analysis_block}
|
||||
|
||||
# Bad example — describes what happened
|
||||
"S&P +5.2% 1m and Nasdaq +8.8% 1m diverge from FTSE -3.4% and Euro Stoxx \
|
||||
-2.6%. The US-vs-rest gap is widening."
|
||||
|
||||
# Good example — interprets what it means
|
||||
"The US-vs-rest equity gap is funded by AI-capex concentration in 7 names; \
|
||||
the breadth-weighted RSP barely keeps pace with SPY, which is the classic \
|
||||
late-cycle marker — narrow leadership, not broad recovery. The 5% 1m gap \
|
||||
between Nasdaq and FTSE is a narrative trade, not a fundamental one."
|
||||
"""
|
||||
|
||||
|
||||
def build_summary_user_prompt(group_name: str, quotes: list[dict]) -> str:
|
||||
parts = [
|
||||
f"# Group: {group_name}",
|
||||
"Indicators (latest reading + 1d/1m/1y/since-anchor change):",
|
||||
"```json",
|
||||
json.dumps(quotes, indent=2, default=str)[:12000],
|
||||
"```",
|
||||
"\nWrite the 2-3 sentence read for this group now.",
|
||||
]
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
def build_aggregate_summary_system_prompt(tone: str, analysis: str) -> str:
|
||||
"""System prompt for the cross-group aggregate read shown on the dashboard.
|
||||
Wider lens than a per-group summary — synthesise across all groups."""
|
||||
tone_block = _TONE.get(tone.upper(), _TONE["INTERMEDIATE"])
|
||||
analysis_block = _ANALYSIS.get(analysis.upper(), _ANALYSIS["SPECULATIVE"])
|
||||
return f"""You write a single SHORT cross-asset INTERPRETATION (≤80 \
|
||||
words, 2-4 sentences) for the dashboard header. The reader is glancing — \
|
||||
give them the meaning of the whole tape, not a recap.
|
||||
|
||||
# What this is for
|
||||
The reader can see every indicator on the dashboard below this paragraph. \
|
||||
Your job is NOT to summarise the moves. It is to explain what the moves, \
|
||||
**taken together as a system**, mean: which regime is being signalled, \
|
||||
which divergences are load-bearing, what fundamental story the cross-asset \
|
||||
behaviour tells.
|
||||
|
||||
# Hard constraints
|
||||
- Plain prose, ONE paragraph. No markdown, headers, lists, or labels.
|
||||
- Open IMMEDIATELY with substance. NEVER start with: "I need to", "I'll", \
|
||||
"We need to", "Here's", "Let me", "Looking at", "Based on", "Sure", "Summary:", \
|
||||
"The data shows", "Across the board". No meta-commentary.
|
||||
- Identify the single most important **cross-asset implication**: e.g. \
|
||||
"rates and credit disagree", "equities outrun fundamentals", "geopolitical \
|
||||
risk premium is in commodities but not vol". Cite no more than 3 specific \
|
||||
numbers, and only as anchors for the interpretation.
|
||||
- Multi-week / multi-month horizon. 1-day moves under 2% are noise.
|
||||
- No buy/sell language. No predictions of specific levels.
|
||||
|
||||
{tone_block}
|
||||
|
||||
{analysis_block}
|
||||
|
||||
# Bad example — describes
|
||||
"Equities are up, real yields are higher, HY OAS is tight, breadth is \
|
||||
narrowing."
|
||||
|
||||
# Good example — interprets
|
||||
"The tape is paying a rising real discount rate (US 10y real +15bp 1m) with \
|
||||
conviction for AI growth, but credit refuses to confirm and breadth is \
|
||||
narrowing — that combination is what late-cycle looks like, not pre-crash. \
|
||||
The risk is not the level but the convergence: if any one of credit, \
|
||||
breadth, or vol turns, the others will follow fast."
|
||||
"""
|
||||
|
||||
|
||||
def build_aggregate_summary_user_prompt(quotes_by_group: dict[str, list[dict]]) -> str:
|
||||
parts = [
|
||||
"# All indicator groups (latest readings + change windows)",
|
||||
"```json",
|
||||
json.dumps(quotes_by_group, indent=2, default=str)[:20000],
|
||||
"```",
|
||||
"\nWrite the cross-asset aggregate read now.",
|
||||
]
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
def build_chat_system_prompt(
|
||||
tone: str,
|
||||
analysis: str,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue