spb/vquant Public MIT
VibeQuant — AI-powered institutional-grade financial intelligence platform.
TypeScript 84.3%
Python 11.7%
JavaScript 1.6%
CSS 1.5%
HTML 0.7%
1/*2 * =============================================================================3 * VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 * File: client/src/components/market-ticker.tsx6 *7 * Author: Simon-Pierre Boucher8 * Contact: contact@spboucher.ai9 * Website: https://www.spboucher.ai10 * Demo: https://www.vquant.ai11 * License: MIT (see LICENSE)12 *13 * Copyright © 2026 Simon-Pierre Boucher. All rights reserved.14 * =============================================================================15 */1617import { useQuery } from "@tanstack/react-query";18import { TrendingUp, TrendingDown } from "lucide-react";1920interface MarketIndex {21 symbol: string;22 name: string;23 price: number;24 change: number;25 changesPercentage: number;26}2728export function MarketTicker() {29 const { data: marketIndices } = useQuery({30 queryKey: ['market-ticker'],31 queryFn: async () => {32 const symbols = [33 '^GSPC', // S&P 50034 '^DJI', // Dow Jones35 '^IXIC', // NASDAQ36 '^RUT', // Russell 200037 '^FTSE', // FTSE 10038 '^GDAXI', // DAX39 '^FCHI', // CAC 4040 '^N225', // Nikkei 22541 '^HSI', // Hang Seng42 '^GSPTSE' // S&P/TSX43 ];4445 const symbolsParam = symbols.join(',');46 const res = await fetch(`https://financialmodelingprep.com/stable/quote?symbol=${symbolsParam}&apikey=${import.meta.env.VITE_FMP_API_KEY || 'JeWwQMjWS3H6hBGHaVxKy1WfGyU5ZeFq'}`);4748 if (!res.ok) throw new Error('Failed to fetch market indices');49 const data = await res.json();5051 return data52 .filter((index: any) => index && index.symbol && index.price != null)53 .map((index: any) => ({54 symbol: index.symbol,55 name: index.name || index.symbol,56 price: Number(index.price) || 0,57 change: Number(index.change) || 0,58 changesPercentage: Number(index.changesPercentage) || 0,59 }));60 },61 refetchInterval: 30000, // Refresh every 30 seconds62 });6364 if (!marketIndices || marketIndices.length === 0) {65 return null;66 }6768 // Duplicate the data to create seamless loop69 const tickerData = [...marketIndices, ...marketIndices];7071 return (72 <div className="w-full bg-muted/30 border-b border-border/40 overflow-hidden backdrop-blur-sm">73 <div className="relative h-10 flex items-center">74 <div className="animate-ticker flex items-center gap-8 whitespace-nowrap">75 {tickerData.map((index: MarketIndex, i) => {76 const isPositive = index.changesPercentage >= 0;77 const displaySymbol = index.symbol.replace('^', '');7879 return (80 <div81 key={`${index.symbol}-${i}`}82 className="flex items-center gap-2 px-4 py-1 rounded-md bg-card/50 backdrop-blur-sm border border-border/30"83 >84 <span className="font-bold text-sm text-primary">85 {displaySymbol}86 </span>87 <span className="text-sm font-semibold text-foreground">88 {index.price.toLocaleString('en-US', {89 minimumFractionDigits: 2,90 maximumFractionDigits: 291 })}92 </span>93 <div className={`flex items-center gap-1 text-xs font-semibold ${94 isPositive ? 'text-green-500' : 'text-red-500'95 }`}>96 {isPositive ? (97 <TrendingUp className="w-3 h-3" />98 ) : (99 <TrendingDown className="w-3 h-3" />100 )}101 <span>102 {isPositive ? '+' : ''}{index.changesPercentage.toFixed(2)}%103 </span>104 </div>105 </div>106 );107 })}108 </div>109 </div>110111 <style>{`112 @keyframes ticker {113 0% {114 transform: translateX(0);115 }116 100% {117 transform: translateX(-50%);118 }119 }120121 .animate-ticker {122 animation: ticker 60s linear infinite;123 }124125 .animate-ticker:hover {126 animation-play-state: paused;127 }128 `}</style>129 </div>130 );131}132