feat: intraday hourly price bars via Polygon range endpoint
- New 'intraday_bars' endpoint in PolygonMarketAdapter: fetches hourly bars for today using range_bars URL with timespan=hour, sort=asc - Scheduler expands intraday_bars global source into per-ticker jobs for all active companies (every 15 minutes via polling_interval) - Migration 025 inserts the intraday source with 900s cadence - Frontend price matching uses closest-timestamp instead of date-string matching, with 2h tolerance for intraday and 36h for daily windows - Bumped market price fetch limit to 200 for intraday granularity
This commit is contained in:
@@ -43,7 +43,7 @@ export function CompanyDetailPage() {
|
||||
const { data: decisions } = useCorporateDecisions(company?.ticker);
|
||||
const { data: trends } = useTrends({ ticker: company?.ticker, limit: 200 });
|
||||
const { data: trendHistory } = useTrendHistory({ ticker: company?.ticker, limit: 500 });
|
||||
const { data: marketPrices } = useMarketPrices(company?.ticker);
|
||||
const { data: marketPrices } = useMarketPrices(company?.ticker, 200);
|
||||
const [tab, setTab] = useState<'trends' | 'sources' | 'aliases' | 'macro' | 'competitors' | 'patterns' | 'signals' | 'decisions'>('trends');
|
||||
|
||||
if (isLoading || !company) return <LoadingSpinner />;
|
||||
@@ -595,21 +595,33 @@ function TrendHistoryChart({ trends, latestTrends, ticker, marketPrices }: { tre
|
||||
.filter((t) => t.entity_id === ticker && t.window === selectedWindow)
|
||||
.sort((a, b) => new Date(a.generated_at).getTime() - new Date(b.generated_at).getTime());
|
||||
|
||||
// Build a price lookup by date (closest price per day)
|
||||
const priceByDay = new Map<string, number>();
|
||||
for (const p of marketPrices ?? []) {
|
||||
if (p.bar_timestamp && p.close != null) {
|
||||
const d = new Date(p.bar_timestamp).toISOString().slice(0, 10);
|
||||
priceByDay.set(d, p.close);
|
||||
// Build a price lookup — match by closest timestamp to each trend point
|
||||
const sortedPrices = [...(marketPrices ?? [])]
|
||||
.filter((p) => p.bar_timestamp != null && p.close != null)
|
||||
.sort((a, b) => a.bar_timestamp - b.bar_timestamp);
|
||||
|
||||
function findClosestPrice(ts: number): number | undefined {
|
||||
if (sortedPrices.length === 0) return undefined;
|
||||
let best = sortedPrices[0];
|
||||
let bestDiff = Math.abs(ts - best.bar_timestamp);
|
||||
for (const p of sortedPrices) {
|
||||
const diff = Math.abs(ts - p.bar_timestamp);
|
||||
if (diff < bestDiff) {
|
||||
best = p;
|
||||
bestDiff = diff;
|
||||
}
|
||||
}
|
||||
// Only match if within 2 hours (for intraday) or 36 hours (for daily)
|
||||
const maxGap = selectedWindow === 'intraday' ? 2 * 3600_000 : 36 * 3600_000;
|
||||
return bestDiff <= maxGap ? best.close : undefined;
|
||||
}
|
||||
|
||||
const chartData: ChartPoint[] = filtered.map((t) => {
|
||||
const trendDate = new Date(t.generated_at).toISOString().slice(0, 10);
|
||||
const price = priceByDay.get(trendDate);
|
||||
const trendTs = new Date(t.generated_at).getTime();
|
||||
const price = findClosestPrice(trendTs);
|
||||
return {
|
||||
time: new Date(t.generated_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }),
|
||||
timestamp: new Date(t.generated_at).getTime(),
|
||||
timestamp: trendTs,
|
||||
strength: +(t.trend_strength * 100).toFixed(1),
|
||||
confidence: +(t.confidence * 100).toFixed(1),
|
||||
contradiction: +(t.contradiction_score * 100).toFixed(1),
|
||||
|
||||
Reference in New Issue
Block a user