add earnings-date guard — warn when a report falls in the target horizon
Finnhub's earnings calendar now supplies next_earnings_date through the fundamentals chain; persisted on fundamental_data (migration 006) and exposed in the fundamentals API. The recommendation panel warns when earnings fall within the ~30-day target horizon (a report can gap price through stop/target) and otherwise shows the next date. Informational only. Deploy: run alembic upgrade (new fundamental_data.next_earnings_date column). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -12,8 +12,19 @@ interface RecommendationPanelProps {
|
||||
longSetup?: TradeSetup;
|
||||
shortSetup?: TradeSetup;
|
||||
currentPrice?: number;
|
||||
nextEarningsDate?: string | null;
|
||||
}
|
||||
|
||||
/** Whole days from today until an ISO date (negative if past). */
|
||||
function daysUntil(iso: string): number | null {
|
||||
const t = new Date(iso).getTime();
|
||||
if (Number.isNaN(t)) return null;
|
||||
return Math.ceil((t - Date.now()) / 86_400_000);
|
||||
}
|
||||
|
||||
/** Earnings within the ~30-day target horizon can gap price through stop/target. */
|
||||
const EARNINGS_HORIZON_DAYS = 30;
|
||||
|
||||
/**
|
||||
* How far current price has drifted from the setup's entry. A setup whose
|
||||
* entry is far from the live price (price already ran toward target, or fell
|
||||
@@ -215,10 +226,11 @@ function RiskSettingsBar({ risk, update }: { risk: RiskSettings; update: (p: Par
|
||||
);
|
||||
}
|
||||
|
||||
export function RecommendationPanel({ symbol, longSetup, shortSetup, currentPrice }: RecommendationPanelProps) {
|
||||
export function RecommendationPanel({ symbol, longSetup, shortSetup, currentPrice, nextEarningsDate }: RecommendationPanelProps) {
|
||||
const { settings: risk, update: updateRisk } = useRiskSettings();
|
||||
const regime = useMarketRegime().data;
|
||||
const summary = longSetup?.recommendation_summary ?? shortSetup?.recommendation_summary;
|
||||
const earningsDays = nextEarningsDate ? daysUntil(nextEarningsDate) : null;
|
||||
const action = (summary?.action ?? 'NEUTRAL') as TradeSetup['recommended_action'];
|
||||
const preferredDirection = recommendationActionDirection(action);
|
||||
|
||||
@@ -261,6 +273,17 @@ export function RecommendationPanel({ symbol, longSetup, shortSetup, currentPric
|
||||
|
||||
<RiskSettingsBar risk={risk} update={updateRisk} />
|
||||
|
||||
{earningsDays != null && earningsDays >= 0 && (
|
||||
earningsDays <= EARNINGS_HORIZON_DAYS ? (
|
||||
<p className="rounded border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-[11px] text-amber-300">
|
||||
⚠ Earnings in {earningsDays} day{earningsDays === 1 ? '' : 's'} ({nextEarningsDate}) — inside the ~30-day
|
||||
target horizon. A report can gap price through your stop or target; consider waiting or sizing down.
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-[11px] text-gray-500">Next earnings: {nextEarningsDate} ({earningsDays} days).</p>
|
||||
)
|
||||
)}
|
||||
|
||||
{preferredDirection !== 'neutral' && preferredSetup ? (
|
||||
<div className="space-y-3">
|
||||
<SetupCard setup={preferredSetup} action={action} currentPrice={currentPrice} risk={risk} regime={regime} />
|
||||
|
||||
@@ -285,6 +285,7 @@ export interface FundamentalResponse {
|
||||
revenue_growth: number | null;
|
||||
earnings_surprise: number | null;
|
||||
market_cap: number | null;
|
||||
next_earnings_date: string | null;
|
||||
fetched_at: string | null;
|
||||
unavailable_fields: Record<string, string>;
|
||||
}
|
||||
|
||||
@@ -262,6 +262,7 @@ export default function TickerDetailPage() {
|
||||
longSetup={longSetup}
|
||||
shortSetup={shortSetup}
|
||||
currentPrice={priceInfo?.price}
|
||||
nextEarningsDate={fundamentals.data?.next_earnings_date}
|
||||
/>
|
||||
|
||||
{/* Chart — always visible */}
|
||||
|
||||
Reference in New Issue
Block a user