Skip to content

Commit 3209544

Browse files
committed
Improve 1D price accuracy and daily change calculations
Refines 1D period handling by using previous close as the reference price and incorporating real-time trade/quote data for more accurate price and daily change calculations. Updates StandaloneWatchlist to prefer latest trade price and use previous close for daily change, and adds debug logging to PerformanceChart and portfolio-data for easier troubleshooting. Also ensures schedule modal resets existing schedule state on close.
1 parent 562e105 commit 3209544

File tree

4 files changed

+149
-30
lines changed

4 files changed

+149
-30
lines changed

src/components/PerformanceChart.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,18 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
307307
parseFloat(String(latestValue.pnlPercent)).toFixed(2) :
308308
(firstValue.value > 0 ? ((totalReturn / firstValue.value) * 100).toFixed(2) : '0.00');
309309
const isPositive = totalReturn >= 0;
310+
311+
// Debug log for 1D period
312+
if (selectedStock && selectedPeriod === '1D' && currentData.length > 0) {
313+
console.log(`[PerformanceChart] ${selectedStock} 1D data:`, {
314+
firstValue: firstValue.value,
315+
latestValue: latestValue.value,
316+
pnl: latestValue.pnl,
317+
pnlPercent: latestValue.pnlPercent,
318+
calculatedReturn: totalReturn,
319+
calculatedPercent: totalReturnPercent
320+
});
321+
}
310322

311323
// Calculate dynamic Y-axis domain for better visibility of small changes
312324
const getYAxisDomain = useCallback(() => {
@@ -617,6 +629,12 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
617629
${Math.abs(totalReturn).toFixed(2)}
618630
({totalReturn >= 0 ? '+' : ''}
619631
{totalReturnPercent}%)
632+
{/* Debug info */}
633+
{selectedPeriod === '1D' && (
634+
<span className="text-xs block text-muted-foreground">
635+
(Last: ${latestValue.value?.toFixed(2)}, Ref: ${(latestValue.value - totalReturn).toFixed(2)})
636+
</span>
637+
)}
620638
</>
621639
) : (
622640
'Loading...'

src/components/StandaloneWatchlist.tsx

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -176,27 +176,38 @@ export default function StandaloneWatchlist({ onSelectStock, selectedStock }: St
176176
updates.description = data.asset.name;
177177
}
178178

179-
// Add price data from quote
180-
if (data.quote) {
181-
const currentPrice = data.quote.ap || data.quote.bp || 0;
179+
// Add price data - prefer latest trade over quote for accuracy
180+
let currentPrice = 0;
181+
182+
// First try to use latest trade price (most accurate)
183+
if (data.latestTrade?.p) {
184+
currentPrice = data.latestTrade.p;
185+
console.log(`[StandaloneWatchlist] ${item.ticker}: Using latest trade price: $${currentPrice}`);
186+
}
187+
// Fallback to quote if no trade available
188+
else if (data.quote) {
189+
// Use mid-point of bid/ask for better accuracy
190+
const bid = data.quote.bp || 0;
191+
const ask = data.quote.ap || 0;
192+
if (bid > 0 && ask > 0) {
193+
currentPrice = (bid + ask) / 2;
194+
} else {
195+
currentPrice = bid || ask || 0;
196+
}
197+
console.log(`[StandaloneWatchlist] ${item.ticker}: Using quote price: $${currentPrice} (bid=${bid}, ask=${ask})`);
198+
}
199+
200+
if (currentPrice > 0) {
182201
updates.currentPrice = currentPrice;
183202

184-
// Calculate today's change from open (during market hours)
185-
// Use currentBar (today's bar) instead of previousBar
186-
if (data.currentBar) {
187-
const todayOpen = data.currentBar.o; // Today's open price
188-
const dayChange = currentPrice - todayOpen;
189-
const dayChangePercent = todayOpen > 0 ? (dayChange / todayOpen) * 100 : 0;
190-
updates.priceChange = dayChange;
191-
updates.priceChangePercent = dayChangePercent;
192-
console.log(`${item.ticker}: Open: ${todayOpen}, Current: ${currentPrice}, Change: ${dayChange} (${dayChangePercent.toFixed(2)}%)`);
193-
} else if (data.previousBar) {
194-
// Fallback to previous close if no current bar (market closed)
203+
// Calculate daily change from previous close (standard market calculation)
204+
if (data.previousBar) {
195205
const previousClose = data.previousBar.c;
196206
const dayChange = currentPrice - previousClose;
197207
const dayChangePercent = previousClose > 0 ? (dayChange / previousClose) * 100 : 0;
198208
updates.priceChange = dayChange;
199209
updates.priceChangePercent = dayChangePercent;
210+
console.log(`[StandaloneWatchlist] ${item.ticker}: previousClose=${previousClose}, dayChange=${dayChange} (${dayChangePercent.toFixed(2)}%)`)
200211
} else {
201212
updates.priceChange = 0;
202213
updates.priceChangePercent = 0;
@@ -496,20 +507,30 @@ export default function StandaloneWatchlist({ onSelectStock, selectedStock }: St
496507

497508
const updates: Partial<WatchlistItem> = {};
498509

499-
// Update price data from quote
500-
if (data.quote) {
501-
const currentPrice = data.quote.ap || data.quote.bp || 0;
510+
// Update price data - prefer latest trade over quote for accuracy
511+
let currentPrice = 0;
512+
513+
// First try to use latest trade price (most accurate)
514+
if (data.latestTrade?.p) {
515+
currentPrice = data.latestTrade.p;
516+
}
517+
// Fallback to quote if no trade available
518+
else if (data.quote) {
519+
// Use mid-point of bid/ask for better accuracy
520+
const bid = data.quote.bp || 0;
521+
const ask = data.quote.ap || 0;
522+
if (bid > 0 && ask > 0) {
523+
currentPrice = (bid + ask) / 2;
524+
} else {
525+
currentPrice = bid || ask || 0;
526+
}
527+
}
528+
529+
if (currentPrice > 0) {
502530
updates.currentPrice = currentPrice;
503531

504-
// Calculate today's change from open (during market hours)
505-
if (data.currentBar) {
506-
const todayOpen = data.currentBar.o; // Today's open price
507-
const dayChange = currentPrice - todayOpen;
508-
const dayChangePercent = todayOpen > 0 ? (dayChange / todayOpen) * 100 : 0;
509-
updates.priceChange = dayChange;
510-
updates.priceChangePercent = dayChangePercent;
511-
} else if (data.previousBar) {
512-
// Fallback to previous close if no current bar (market closed)
532+
// Calculate daily change from previous close (standard market calculation)
533+
if (data.previousBar) {
513534
const previousClose = data.previousBar.c;
514535
const dayChange = currentPrice - previousClose;
515536
const dayChangePercent = previousClose > 0 ? (dayChange / previousClose) * 100 : 0;

src/components/schedule-rebalance/hooks/useScheduleData.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ export function useScheduleData(isOpen: boolean, scheduleId: string | null = nul
275275
setSelectedPositions(new Set());
276276
setIncludeWatchlist(false);
277277
setWatchlistStocks([]);
278+
setExistingSchedule(null); // Clear existing schedule when modal closes
278279
}
279280
}, [isOpen]);
280281

src/lib/portfolio-data.ts

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,8 @@ export const fetchPortfolioData = async (): Promise<PortfolioData> => {
294294
// Helper to convert Alpaca bar data to our format
295295
const convertBarsToDataPoints = (
296296
bars: any[],
297-
period: string
297+
period: string,
298+
previousClose?: number
298299
): PortfolioDataPoint[] => {
299300
if (!bars || bars.length === 0) {
300301
console.log(`No bars to convert for period ${period}`);
@@ -311,7 +312,13 @@ const convertBarsToDataPoints = (
311312
return [];
312313
}
313314

314-
const referencePrice = firstPrice;
315+
// For 1D period with previous close available, use it as reference
316+
// Otherwise use the first bar's close price
317+
const referencePrice = (period === '1D' && previousClose !== undefined) ? previousClose : firstPrice;
318+
319+
if (period === '1D') {
320+
console.log(`[convertBarsToDataPoints] ${period}: firstPrice=${firstPrice}, previousClose=${previousClose}, using referencePrice=${referencePrice}`);
321+
}
315322

316323
return bars.map((bar: any) => {
317324
const price = bar.c !== undefined ? bar.c : bar.close;
@@ -487,8 +494,44 @@ export const fetchStockDataForPeriod = async (ticker: string, period: string): P
487494
console.log(`1D data filtered: ${bars.length} -> ${filteredBars.length} bars`);
488495
}
489496

497+
// For 1D period, fetch previous close and current quote for accurate daily change calculation
498+
let previousClose: number | undefined;
499+
let currentQuotePrice: number | undefined;
500+
if (period === '1D') {
501+
try {
502+
// Get batch data with previous bar and current quote for daily change calculation
503+
const batchData = await alpacaAPI.getBatchData([ticker], {
504+
includeQuotes: true,
505+
includeBars: true
506+
});
507+
508+
if (batchData[ticker]?.previousBar) {
509+
previousClose = batchData[ticker].previousBar.c;
510+
console.log(`Using previous close for ${ticker}: $${previousClose}`);
511+
}
512+
513+
// Prefer latest trade price over quote for accuracy
514+
if (batchData[ticker]?.latestTrade?.p) {
515+
currentQuotePrice = batchData[ticker].latestTrade.p;
516+
console.log(`Current trade price for ${ticker}: $${currentQuotePrice}`);
517+
} else if (batchData[ticker]?.quote) {
518+
// Use mid-point of bid/ask for better accuracy
519+
const bid = batchData[ticker].quote.bp || 0;
520+
const ask = batchData[ticker].quote.ap || 0;
521+
if (bid > 0 && ask > 0) {
522+
currentQuotePrice = (bid + ask) / 2;
523+
} else {
524+
currentQuotePrice = bid || ask || undefined;
525+
}
526+
console.log(`Current quote for ${ticker}: $${currentQuotePrice} (bid=${bid}, ask=${ask})`);
527+
}
528+
} catch (err) {
529+
console.warn(`Could not fetch previous close/quote for ${ticker}:`, err);
530+
}
531+
}
532+
490533
// Convert and downsample the data for display
491-
let fullData = convertBarsToDataPoints(filteredBars, period);
534+
let fullData = convertBarsToDataPoints(filteredBars, period, previousClose);
492535

493536
// For YTD, filter to current year
494537
if (period === 'YTD' && filteredBars.length > 0) {
@@ -498,8 +541,44 @@ export const fetchStockDataForPeriod = async (ticker: string, period: string): P
498541
new Date(bar.t || bar.timestamp).getTime() >= yearStartTime
499542
);
500543
if (ytdBars.length > 0) {
501-
fullData = convertBarsToDataPoints(ytdBars, period);
544+
fullData = convertBarsToDataPoints(ytdBars, period, undefined);
545+
}
546+
}
547+
548+
// For 1D period with current quote, add/update the last data point with real-time price
549+
if (period === '1D' && currentQuotePrice !== undefined && fullData.length > 0) {
550+
const referencePrice = previousClose || fullData[0].value;
551+
const currentPnl = currentQuotePrice - referencePrice;
552+
const currentPnlPercent = referencePrice !== 0 ? (currentPnl / referencePrice) * 100 : 0;
553+
554+
// Add a current data point with the real-time quote
555+
const now = new Date();
556+
const currentTime = formatDate(now, period);
557+
558+
// Check if the last point is recent (within last 5 minutes)
559+
const lastPoint = fullData[fullData.length - 1];
560+
const lastTime = lastPoint.time;
561+
562+
// Update or add the current price point
563+
if (lastTime === currentTime || fullData.length === 0) {
564+
// Update the last point with current quote
565+
fullData[fullData.length - 1] = {
566+
time: currentTime,
567+
value: currentQuotePrice,
568+
pnl: currentPnl,
569+
pnlPercent: currentPnlPercent
570+
};
571+
} else {
572+
// Add new point for current quote
573+
fullData.push({
574+
time: currentTime,
575+
value: currentQuotePrice,
576+
pnl: currentPnl,
577+
pnlPercent: currentPnlPercent
578+
});
502579
}
580+
581+
console.log(`Added/updated current quote to 1D data: price=$${currentQuotePrice}, pnl=$${currentPnl.toFixed(2)}, pnlPercent=${currentPnlPercent.toFixed(2)}%`);
503582
}
504583

505584
return downsampleData(fullData, period);

0 commit comments

Comments
 (0)