mirror of
https://github.com/caty21/forex-dashboard.git
synced 2026-08-25 10:28:05 +00:00
auto: sync 2026-06-03 00:02
This commit is contained in:
@@ -608,13 +608,21 @@ export default function CurrencyCard({
|
|||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
{ratePath.peakMeeting && (
|
{ratePath.peakMeeting && (
|
||||||
<div className="flex items-center justify-between mt-1 text-[10px]">
|
<div className="flex items-center justify-between mt-1 text-[10px]">
|
||||||
<span className="text-slate-600">Pic : <span className="text-slate-400">{ratePath.peakMeeting.label}</span></span>
|
<span className="text-slate-600">
|
||||||
|
Pic : <span className="text-slate-400">{ratePath.peakMeeting.label}</span>
|
||||||
|
</span>
|
||||||
<span className={ratePath.peakMeeting.probIsCut ? "text-sky-400 font-bold" : "text-red-400 font-bold"}>
|
<span className={ratePath.peakMeeting.probIsCut ? "text-sky-400 font-bold" : "text-red-400 font-bold"}>
|
||||||
{ratePath.peakMeeting.probMovePct.toFixed(0)}% {ratePath.peakMeeting.probIsCut ? "Cut" : "Hike"}
|
{ratePath.peakMeeting.probMovePct.toFixed(0)}% {ratePath.peakMeeting.probIsCut ? "Cut" : "Hike"}
|
||||||
</span>
|
</span>
|
||||||
{ratePath.yearEndImplied !== null && (
|
{ratePath.yearEndImplied !== null && (() => {
|
||||||
<span className="text-slate-600">fin an: {ratePath.yearEndImplied.toFixed(2)}%</span>
|
const bps = Math.round((ratePath.yearEndImplied! - ratePath.currentRate) * 100);
|
||||||
)}
|
const cls = bps < 0 ? "text-sky-400 font-bold" : bps > 0 ? "text-red-400 font-bold" : "text-slate-500";
|
||||||
|
return (
|
||||||
|
<span className={cls} title={`Taux actuel: ${ratePath.currentRate.toFixed(2)}% → fin an: ${ratePath.yearEndImplied!.toFixed(2)}%`}>
|
||||||
|
{bps > 0 ? "+" : ""}{bps}bps fin an
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+12
-2
@@ -64,7 +64,12 @@ function parseArticleBody(text: string, publishedDate: string): ILExpectationsMa
|
|||||||
|
|
||||||
// Matches: "RBNZ: 75 bps (79% probability of rate hike at the next meeting)"
|
// Matches: "RBNZ: 75 bps (79% probability of rate hike at the next meeting)"
|
||||||
// "Fed: 13 bps (99% probability of no change at the next meeting)"
|
// "Fed: 13 bps (99% probability of no change at the next meeting)"
|
||||||
const linePattern = /([A-Za-z]+)\s*:\s*(\d+)\s*bps\s*\(\s*(\d+)%\s*probability\s+of\s+(rate\s+(?:hike|cut)|no\s+change)\s+at\s+the\s+next\s+meeting\)/gi;
|
// "ECB: 53 bps (99% probability of rate cut at the next meeting)"
|
||||||
|
// Note: bps value in the article is always unsigned — sign is inferred from direction.
|
||||||
|
// rate hike → positive bps (rate going up)
|
||||||
|
// rate cut → negative bps (rate going down)
|
||||||
|
// no change → keep unsigned (residual expectation for later meetings)
|
||||||
|
const linePattern = /([A-Za-z]+)\s*:\s*(-?\d+)\s*bps\s*\(\s*(\d+)%\s*probability\s+of\s+(rate\s+(?:hike|cut)|no\s+change)\s+at\s+the\s+next\s+meeting\)/gi;
|
||||||
|
|
||||||
let m: RegExpExecArray | null;
|
let m: RegExpExecArray | null;
|
||||||
while ((m = linePattern.exec(text)) !== null) {
|
while ((m = linePattern.exec(text)) !== null) {
|
||||||
@@ -72,11 +77,16 @@ function parseArticleBody(text: string, publishedDate: string): ILExpectationsMa
|
|||||||
const ccy = CB_TO_CCY[cbRaw.toLowerCase()];
|
const ccy = CB_TO_CCY[cbRaw.toLowerCase()];
|
||||||
if (!ccy) continue;
|
if (!ccy) continue;
|
||||||
|
|
||||||
const bpsYearEnd = parseInt(bpsStr);
|
|
||||||
const probPct = parseInt(probStr);
|
const probPct = parseInt(probStr);
|
||||||
const direction = directionRaw.toLowerCase();
|
const direction = directionRaw.toLowerCase();
|
||||||
const nextMeetingIsNoChange = direction === "no change";
|
const nextMeetingIsNoChange = direction === "no change";
|
||||||
const nextMeetingIsHike = !nextMeetingIsNoChange && direction.includes("hike");
|
const nextMeetingIsHike = !nextMeetingIsNoChange && direction.includes("hike");
|
||||||
|
const nextMeetingIsCut = !nextMeetingIsNoChange && direction.includes("cut");
|
||||||
|
|
||||||
|
// Apply sign: if direction is cut and value is positive, negate it
|
||||||
|
let bpsYearEnd = parseInt(bpsStr);
|
||||||
|
if (nextMeetingIsCut && bpsYearEnd > 0) bpsYearEnd = -bpsYearEnd;
|
||||||
|
|
||||||
// Probability of change = 100 - probNoChange OR directProb if it's a hike/cut
|
// Probability of change = 100 - probNoChange OR directProb if it's a hike/cut
|
||||||
const nextMeetingProbPct = nextMeetingIsNoChange ? 100 - probPct : probPct;
|
const nextMeetingProbPct = nextMeetingIsNoChange ? 100 - probPct : probPct;
|
||||||
|
|
||||||
|
|||||||
@@ -214,6 +214,21 @@ export async function fetchAllCBPaths(): Promise<RateProbData> {
|
|||||||
if (snbPath) data["CHF"] = snbPath;
|
if (snbPath) data["CHF"] = snbPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enrichir yearEndImplied avec bpsYearEnd de IL (Giuseppe Dellamotta — source humaine)
|
||||||
|
// pour toutes les devises où IL a une donnée ET rateprobability.com a réussi.
|
||||||
|
// Règle : pour les hikes (bpsYearEnd > 0) et cuts (bpsYearEnd < 0), mettre à jour.
|
||||||
|
// Pour "no change" (bpsYearEnd proche de 0), garder la valeur rateprobability.com.
|
||||||
|
for (const [ccyStr, ilEntry] of Object.entries(ilData)) {
|
||||||
|
const ccy = ccyStr as keyof RateProbData;
|
||||||
|
const path = data[ccy];
|
||||||
|
if (!path) continue;
|
||||||
|
if (typeof ilEntry.bpsYearEnd !== "number") continue;
|
||||||
|
if (ilEntry.nextMeetingIsNoChange && Math.abs(ilEntry.bpsYearEnd) < 10) continue; // garder RP si "no change" + bps résiduel faible
|
||||||
|
|
||||||
|
const ilYearEnd = parseFloat((path.currentRate + ilEntry.bpsYearEnd / 100).toFixed(4));
|
||||||
|
data[ccy] = { ...path, yearEndImplied: ilYearEnd };
|
||||||
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user