fix(B4): show latest bar timestamp in ticker header

This commit is contained in:
Saravana Achu Mac 2026-05-04 17:10:57 -07:00
parent bed7f83f3c
commit 311d79c4c4
2 changed files with 56 additions and 10 deletions

View File

@ -110,4 +110,19 @@ describe('TickerHeader', () => {
await waitFor(() => expect(screen.getAllByText('Apple Inc.').length).toBeGreaterThan(0));
expect(fetchResearchProfileMock).toHaveBeenCalledTimes(1);
});
it('shows the latest chart bar timestamp instead of the render time', async () => {
fetchResearchProfileMock.mockResolvedValue({ companyName: 'Apple Inc.' });
fetchChartBarsMock.mockResolvedValueOnce([
{ ts: Date.UTC(2024, 0, 3, 19, 30), open: 210, high: 213, low: 209, close: 212, volume: 1000 },
{ ts: Date.UTC(2024, 0, 3, 20, 30), open: 212, high: 214, low: 211, close: 213, volume: 1200 },
]);
renderTickerHeader();
expect(screen.getByText(/Latest bar pending ET/)).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText(/Jan 3, 2024, 03:30 PM ET · NASDAQ/)).toBeInTheDocument();
});
});
});

View File

@ -55,6 +55,19 @@ function formatPriceLabel(ts: number, period: Period) {
return d.toLocaleDateString([], { month: 'short', day: 'numeric' });
}
function formatAsOfTimestamp(ts: number | null) {
if (ts == null) return 'Latest bar pending';
return new Date(ts).toLocaleString('en-US', {
timeZone: 'America/New_York',
month: 'short',
day: 'numeric',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
}
function average(values: number[]) {
return values.reduce((sum, value) => sum + value, 0) / values.length;
}
@ -150,7 +163,15 @@ function normalizeResearchProfile(profile: any): ResearchProfile | null {
}
// ─── Ticker header ────────────────────────────────────────────────────────────
export function TickerHeader({ symbol, profile }: { symbol: string; profile?: ResearchProfile | null }) {
export function TickerHeader({
symbol,
profile,
latestBarTimestamp,
}: {
symbol: string;
profile?: ResearchProfile | null;
latestBarTimestamp?: number | null;
}) {
const navigate = useNavigate();
const { botState } = useAppContext();
const data = botState.symbols?.[symbol];
@ -211,17 +232,20 @@ export function TickerHeader({ symbol, profile }: { symbol: string; profile?: Re
</div>
<div style={{ fontSize: 11, color: '#9CA3AF', marginTop: 3 }}>
{new Date().toLocaleString('en-US', {
month: 'short', day: 'numeric', year: 'numeric',
hour: '2-digit', minute: '2-digit',
})} ET · NASDAQ
{formatAsOfTimestamp(latestBarTimestamp ?? null)} ET · NASDAQ
</div>
</div>
);
}
// ─── Stock chart ──────────────────────────────────────────────────────────────
function StockChart({ symbol }: { symbol: string }) {
function StockChart({
symbol,
onLatestBarTimestamp,
}: {
symbol: string;
onLatestBarTimestamp?: (timestamp: number | null) => void;
}) {
const [period, setPeriod] = useState<Period>('1Y');
const [bars, setBars] = useState<OHLCVBar[]>([]);
const [loading, setLoading] = useState(false);
@ -237,12 +261,18 @@ function StockChart({ symbol }: { symbol: string }) {
setLoading(true);
setError(null);
setBars([]);
onLatestBarTimestamp?.(null);
fetchChartBars(symbol, period)
.then(data => { if (!cancelled) setBars(data); })
.then(data => {
if (!cancelled) {
setBars(data);
onLatestBarTimestamp?.(data.at(-1)?.ts ?? null);
}
})
.catch(err => { if (!cancelled) setError(err?.message ?? 'Failed to load chart'); })
.finally(() => { if (!cancelled) setLoading(false); });
return () => { cancelled = true; };
}, [symbol, period]);
}, [symbol, period, onLatestBarTimestamp]);
const closes = bars.map(b => b.close);
const rsi = calculateRsi(closes);
@ -742,6 +772,7 @@ export function HomeView() {
const { activeSymbol, setActiveSymbol } = useAppContext();
const [profile, setProfile] = useState<ResearchProfile | null>(null);
const [profileLoading, setProfileLoading] = useState(false);
const [latestBarTimestamp, setLatestBarTimestamp] = useState<number | null>(null);
useEffect(() => {
if (!activeSymbol) {
@ -770,8 +801,8 @@ export function HomeView() {
return (
<div>
<TickerHeader symbol={activeSymbol} profile={profile} />
<StockChart symbol={activeSymbol} />
<TickerHeader symbol={activeSymbol} profile={profile} latestBarTimestamp={latestBarTimestamp} />
<StockChart symbol={activeSymbol} onLatestBarTimestamp={setLatestBarTimestamp} />
<QuickStats symbol={activeSymbol} />
<ResearchCards symbol={activeSymbol} profile={profile} profileLoading={profileLoading} />
</div>