mirror of
https://github.com/caty21/forex-dashboard.git
synced 2026-07-27 20:37:45 +00:00
feat: marchés 4H + lien TradingView + cache IL + perf chargement
- charts Marchés : interval D/W → 4H (240), hauteur 220 → 420px, grid 2col - TvAdvancedChart : bouton "↗ TradingView" par chart (outils tracé complets) - cache fichier IL 2h (data/il-enrichment-cache.json) : API rate-proba 13s → <500ms - stale-while-revalidate macro : affiche cache immédiatement, refetch 10s background - seuil cache rate-proba : 48h → 7j pour dev local Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+4
-4
@@ -599,10 +599,10 @@ export default function Dashboard() {
|
||||
<div className="h-px flex-1 bg-sky-500/20" />
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<TvAdvancedChart symbol="FOREXCOM:SPXUSD" label="S&P 500" interval="D" height={220} />
|
||||
<TvAdvancedChart symbol="PEPPERSTONE:VIX" label="VIX" interval="D" height={220} />
|
||||
<TvAdvancedChart symbol="CAPITALCOM:DXY" label="DXY Dollar Index" interval="W" height={220} />
|
||||
<TvAdvancedChart symbol="TVC:GOLD" label="Or (XAU/USD)" interval="W" height={220} />
|
||||
<TvAdvancedChart symbol="FOREXCOM:SPXUSD" label="S&P 500" interval="240" height={420} />
|
||||
<TvAdvancedChart symbol="PEPPERSTONE:VIX" label="VIX" interval="240" height={420} />
|
||||
<TvAdvancedChart symbol="CAPITALCOM:DXY" label="DXY Dollar Index" interval="240" height={420} />
|
||||
<TvAdvancedChart symbol="TVC:GOLD" label="Or (XAU/USD)" interval="240" height={420} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+24
-5
@@ -84,13 +84,16 @@ export function TvAdvancedChart({
|
||||
backgroundColor: "rgba(8,12,20,0)",
|
||||
gridColor: "rgba(30,45,61,0.5)",
|
||||
hide_top_toolbar: false,
|
||||
hide_side_toolbar: false,
|
||||
hide_legend: false,
|
||||
allow_symbol_change: false,
|
||||
calendar: false,
|
||||
hide_volume: false,
|
||||
isTransparent: true,
|
||||
save_image: true,
|
||||
withdateranges: true,
|
||||
drawings_access: { type: "all" },
|
||||
support_host: "https://www.tradingview.com",
|
||||
container_id: containerId,
|
||||
},
|
||||
height
|
||||
@@ -102,13 +105,29 @@ export function TvAdvancedChart({
|
||||
};
|
||||
}, []); // eslint-disable-line
|
||||
|
||||
const tvUrl = `https://www.tradingview.com/chart/?symbol=${encodeURIComponent(symbol)}&interval=${interval}`;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
{label && (
|
||||
<p className="text-[10px] font-semibold text-slate-500 uppercase tracking-wider">
|
||||
{label}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex items-center justify-between">
|
||||
{label && (
|
||||
<p className="text-[10px] font-semibold text-slate-500 uppercase tracking-wider">{label}</p>
|
||||
)}
|
||||
<a
|
||||
href={tvUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-1 text-[9px] text-slate-600 hover:text-sky-400 transition-colors ml-auto"
|
||||
title="Ouvrir dans TradingView (outils de tracé complets)"
|
||||
>
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
||||
<polyline points="15 3 21 3 21 9"/>
|
||||
<line x1="10" y1="14" x2="21" y2="3"/>
|
||||
</svg>
|
||||
TradingView
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
className="tradingview-widget-container rounded-lg overflow-hidden bg-[#0f1623]"
|
||||
|
||||
+27
-3
@@ -3,11 +3,35 @@
|
||||
// Collectées par GitHub Actions (toutes les heures) → data/rate-probabilities.json
|
||||
// InvestingLive (Giuseppe Dellamotta) en enrichissement CHF + deltas hebdo
|
||||
|
||||
import { readFileSync } from "fs";
|
||||
import { readFileSync, writeFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
import type { Currency } from "./types";
|
||||
import { fetchILExpectationsWithHistory } from "./investinglive";
|
||||
import type { ILExpectationsMap } from "./investinglive";
|
||||
import type { ILExpectationsMap, ILExpectationsWithHistory } from "./investinglive";
|
||||
|
||||
// ── Cache fichier pour les données InvestingLive (évite 56 HTTP HEAD par appel) ──
|
||||
|
||||
const IL_CACHE_FILE = join(process.cwd(), "data", "il-enrichment-cache.json");
|
||||
const IL_CACHE_TTL = 2 * 60 * 60 * 1000; // 2h
|
||||
|
||||
async function getCachedILHistory(): Promise<ILExpectationsWithHistory> {
|
||||
try {
|
||||
const raw = readFileSync(IL_CACHE_FILE, "utf8");
|
||||
const cached = JSON.parse(raw) as { ts: number; data: ILExpectationsWithHistory };
|
||||
if (Date.now() - cached.ts < IL_CACHE_TTL) {
|
||||
console.log(`[IL-cache] HIT (${Math.round((Date.now() - cached.ts) / 60000)}min old)`);
|
||||
return cached.data;
|
||||
}
|
||||
console.log("[IL-cache] STALE — refetch");
|
||||
} catch {
|
||||
console.log("[IL-cache] MISS — premier fetch");
|
||||
}
|
||||
const data = await fetchILExpectationsWithHistory();
|
||||
try {
|
||||
writeFileSync(IL_CACHE_FILE, JSON.stringify({ ts: Date.now(), data }));
|
||||
} catch { /* /tmp read-only en prod Vercel — ignoré */ }
|
||||
return data;
|
||||
}
|
||||
|
||||
// ── Types publics ──────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -291,7 +315,7 @@ function parseCBBody(ccy: Currency, body: Record<string, unknown>): CBRatePath |
|
||||
export async function fetchAllCBPaths(): Promise<RateProbData> {
|
||||
// GitHub Actions met à jour data/rate-probabilities.json toutes les heures
|
||||
// (CME FedWatch pour USD, Investing.com pour les autres, InvestingLive en fallback)
|
||||
const [ilHistory] = await Promise.all([fetchILExpectationsWithHistory()]);
|
||||
const [ilHistory] = await Promise.all([getCachedILHistory()]);
|
||||
const ilData = ilHistory.current;
|
||||
const ilPrev = ilHistory.prev;
|
||||
const prevDate = ilHistory.prevDate;
|
||||
|
||||
Reference in New Issue
Block a user