feat: OISEnhancedBlock — rate curve, implied pts, scénarios charts per CB

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
caty21
2026-06-27 14:12:05 +02:00
parent a3d847ce59
commit 9da463a424
12 changed files with 1777 additions and 754 deletions
+9 -1
View File
@@ -845,7 +845,15 @@ export async function fetchAllNews(): Promise<NewsItem[]> {
return true;
});
deduped.sort((a, b) => b.publishedAt.localeCompare(a.publishedAt));
const PRIORITY_SET = new Set(["Discours BC", "Décision Taux", "Crise", "Guerre", "Chef d'État", "Probabilités Taux"]);
const isPrio = (item: NewsItem) => item.categories.some(c => PRIORITY_SET.has(c));
deduped.sort((a, b) => {
const pa = isPrio(a) ? 1 : 0;
const pb = isPrio(b) ? 1 : 0;
if (pb !== pa) return pb - pa;
return b.publishedAt.localeCompare(a.publishedAt);
});
return deduped;
}
+77
View File
@@ -697,3 +697,80 @@ export async function fetchTEGDPGrowthRate(): Promise<CoreCPIMap> {
return {};
}
}
// ── Employment Change — pages individuelles TradingEconomics ─────────────────
// USD : /united-states/non-farm-payrolls → en milliers, MoM
// GBP : /united-kingdom/employment-change → en milliers, MoM (3 mois glissants)
// AUD : /australia/employment-change → en personnes → /1000
// EUR : /euro-area/employment-change → QoQ%
export interface TEEmploymentEntry {
value: number; // k pour USD/GBP/AUD, QoQ% pour EUR
prev: number | null;
}
const TE_EMP_CONFIG: Partial<Record<Currency, { slug: string; unitKw: string; divisor: number; precision: number }>> = {
USD: { slug: "united-states/non-farm-payrolls", unitKw: "thousand", divisor: 1, precision: 0 },
GBP: { slug: "united-kingdom/employment-change", unitKw: "thousand", divisor: 1, precision: 0 },
AUD: { slug: "australia/employment-change", unitKw: "person", divisor: 1000, precision: 1 },
EUR: { slug: "euro-area/employment-change", unitKw: "percent", divisor: 1, precision: 2 },
};
export async function fetchTEEmploymentChange(): Promise<Partial<Record<Currency, TEEmploymentEntry>>> {
const headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
};
const entries = await Promise.all(
(Object.entries(TE_EMP_CONFIG) as [Currency, NonNullable<typeof TE_EMP_CONFIG[Currency]>][])
.map(async ([ccy, { slug, unitKw, divisor, precision }]) => {
try {
const res = await fetch(`https://tradingeconomics.com/${slug}`, {
next: { revalidate: 21600 },
headers,
});
if (!res.ok) return null;
const html = await res.text();
// Current value from meta description
const metaM = html.match(/name=["']description["'][^>]*content=["']([^"']+)["']/i)
?? html.match(/content=["']([^"']+)["'][^>]*name=["']description["']/i);
const desc = metaM?.[1] ?? "";
const incrM = desc.match(/increased\s+by\s+([\d,]+\.?\d*)/i);
const decrM = desc.match(/decreased\s+by\s+([\d,]+\.?\d*)/i);
if (!incrM && !decrM) return null;
const sign = incrM ? 1 : -1;
const absVal = parseFloat((incrM?.[1] ?? decrM![1]).replace(/,/g, ""));
const curRaw = sign * absVal;
// Find previous from table: groups of (value, prev, unit-label)
const tdVals: string[] = [];
const tdRe = /<td[^>]*>([^<]+)<\/td>/g;
let m: RegExpExecArray | null;
while ((m = tdRe.exec(html)) !== null) tdVals.push(m[1].trim());
let prevRaw: number | null = null;
for (let i = 0; i < tdVals.length - 2; i++) {
const a = parseFloat(tdVals[i].replace(/,/g, ""));
const b = parseFloat(tdVals[i + 1].replace(/,/g, ""));
const c = tdVals[i + 2].toLowerCase();
if (isNaN(a) || isNaN(b) || !c.includes(unitKw)) continue;
const tol = Math.max(1, Math.abs(curRaw) * 0.02);
if (Math.abs(a - curRaw) <= tol) { prevRaw = b; break; }
}
return [ccy, {
value: parseFloat((curRaw / divisor).toFixed(precision)),
prev: prevRaw !== null ? parseFloat((prevRaw / divisor).toFixed(precision)) : null,
}] as [Currency, TEEmploymentEntry];
} catch { return null; }
})
);
const result: Partial<Record<Currency, TEEmploymentEntry>> = {};
for (const e of entries) if (e) result[e[0]] = e[1];
return result;
}
+35 -12
View File
@@ -110,7 +110,8 @@ export interface DriverData {
sp500Change: number | null; // pts vs clôture j-1
sp500ChangePct: number | null; // % vs clôture j-1
btc: number | null; // BTC/USD
btcChange24h: number | null; // % variation 24h (CoinGecko)
btcChange24h: number | null; // % variation 24h (legacy)
btcDeltaPct: number | null; // % vs clôture J-1 (Business Insider)
// Crédit
hySpread: number | null;
igSpread: number | null;
@@ -121,14 +122,18 @@ export interface DriverData {
us2y: number | null;
curveSlope: number | null;
// Commodités (avec delta vs session précédente)
gold: number | null;
goldDelta: number | null;
silver: number | null;
silverDelta: number | null;
brent: number | null;
brentDelta: number | null;
gold: number | null;
goldDelta: number | null;
goldDeltaPct: number | null;
silver: number | null;
silverDelta: number | null;
silverDeltaPct: number | null;
brent: number | null;
brentDelta: number | null;
brentDeltaPct: number | null;
wti: number | null;
wtiDelta: number | null;
wtiDeltaPct: number | null;
// Compat
copper: number | null;
}
@@ -144,10 +149,28 @@ export interface SentimentEntry {
pair: string;
}
export type MacroSection = "all" | "inflation" | "pmi" | "employment" | "gdp" | "policy";
export interface CotEntry {
net: number;
longPct: number;
shortPct: number;
totalLev: number;
weekDate: string;
// HF — Leveraged Money (spéculation directionnelle, hedge funds / CTAs)
net: number; // longs - shorts
hfLongs: number; // contrats long bruts
hfShorts: number; // contrats short bruts
longPct: number; // % longs / total HF
shortPct: number; // % shorts / total HF
totalLev: number; // total contrats HF
// AM — Asset Manager (hedging institutionnel, fonds pension / souverains)
amNet: number;
amLongs: number;
amShorts: number;
amLongPct: number; // % longs / total AM
amTotal: number;
// Δ semaine précédente (null si pas de données J-7)
netDelta: number | null; // Δ net HF
longsDelta: number | null; // Δ longs HF (+= ajout de longs)
shortsDelta: number | null; // Δ shorts HF (+= ajout de shorts)
amNetDelta: number | null; // Δ net AM
// Métadonnées
weekDate: string;
prevWeekDate: string | null;
}