mirror of
https://github.com/caty21/forex-dashboard.git
synced 2026-07-27 20:37:45 +00:00
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:
+9
-1
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user