fix: load full watchlist research details

prakhar82 committed Sep 13, 2026 at 15:18 UTC 289d918aaa46f5e4f3655106a828e62837a7e754
2 files changed +181 -16
frontend/app/components/investment-workspace.tsx
+128 -16
@@ -3130,8 +3130,8 @@ function StockResearchDrawer({ position, watchlistItem, research, onUpdateDispla
3130 <section><h3>Overview</h3>{editing && position ? <div className="holding-name-editor"><label>Display name<input maxLength={160} value={name} onChange={(event) => setName(event.target.value)} /></label><Button disabled={saving} onClick={() => void saveName()}>Save</Button><Button variant="secondary" disabled={saving} onClick={() => { setEditing(false); setName(position.customDisplayName ?? position.displayName); setRenameError(null); }}>Cancel</Button>{renameError ? <small role="alert">{renameError}</small> : null}</div> : <p><strong>{displayName}</strong> {position?.sourceType === "MANUAL_CSV_IMPORT" && onUpdateDisplayName ? <button className="text-action" type="button" onClick={() => setEditing(true)}>Edit</button> : null}</p>}<p>{ticker} · {isin ?? "ISIN N/A"} · {market?.resolution.exchange ?? exchange} · {country ?? "Country N/A"} · {currency ?? "Currency N/A"} · {market?.resolution.quoteType ?? assetType}</p><p>Provider ticker: {market?.resolution.providerTicker ?? "N/A"} · Provider identity: {market?.resolution.companyName ?? "N/A"}</p><p>Sector: {metricText(structuredFact(research, "sector"))} · Industry: {metricText(structuredFact(research, "industry"))}</p><p>{position ? <>Broker/source: {brokerDisplayName(position.brokerType)} · {position.sourceType}</> : <>Watchlist status: Not held</>}</p></section>
3131 {position ? <section><h3>Position</h3><div className="research-metric-grid"><div className="research-metric"><span>Quantity</span><strong>{position.quantity}</strong></div><div className="research-metric"><span>Average Cost</span><strong>{formatBackendMoney(position.averageCost)}</strong></div><div className="research-metric"><span>Cost Basis</span><strong>{formatBackendMoney(position.costBasis)}</strong></div><div className="research-metric"><span>Imported Price</span><strong>{formatBackendMoney(position.importedPrice)}</strong></div><div className="research-metric"><span>Latest Market Price</span><strong>{formatBackendMoney(position.currentPrice)}</strong></div><div className="research-metric"><span>Market Value</span><strong>{formatBackendMoney(position.marketValue)}</strong></div><div className="research-metric"><span>Unrealized P/L</span><strong>{formatBackendMoney(position.unrealizedProfitLoss)}</strong></div></div></section> : <section><h3>Watchlist status</h3><p>Public company research</p><div className="research-metric-grid">{watchlistItem?.sourcePeriod ? <div className="research-metric"><span>Market return ({watchlistItem.sourcePeriod})</span><strong className={`${performanceRowTone(watchlistItem.sourcePerformancePct)}-text`}>{formatSignedPerformancePct(watchlistItem.sourcePerformancePct)}</strong></div> : null}</div></section>}
3132 <section><h3>Market Data</h3><div className="research-metric-grid"><div className="research-metric"><span>Latest Price</span><strong>{currentPrice}</strong></div><EvidenceMetric label="Previous close" metric={structuredFact(research, "previousClose")} /><EvidenceMetric label="Bid" metric={structuredFact(research, "bid")} /><EvidenceMetric label="Ask" metric={structuredFact(research, "ask")} /><EvidenceMetric label="Volume" metric={structuredFact(research, "volume")} /><EvidenceMetric label="10-day avg volume" metric={structuredFact(research, "averageVolume10Day")} /><EvidenceMetric label="3-month avg volume" metric={structuredFact(research, "averageVolume")} /><EvidenceMetric label="52-week low" metric={structuredFact(research, "fiftyTwoWeekLow")} /><EvidenceMetric label="52-week high" metric={structuredFact(research, "fiftyTwoWeekHigh")} /><div className="research-metric"><span>Price freshness</span><strong>{research?.priceFreshness ?? "UNKNOWN"}</strong></div><div className="research-metric"><span>Market As Of</span><strong>{market?.marketAsOf ? new Date(market.marketAsOf).toLocaleString() : position?.quote?.sourceTimestamp ? new Date(position.quote.sourceTimestamp).toLocaleString() : "N/A"}</strong></div><div className="research-metric"><span>Retrieved At</span><strong>{market?.retrievedAt ? new Date(market.retrievedAt).toLocaleString() : position?.quote?.receivedAt ? new Date(position.quote.receivedAt).toLocaleString() : "N/A"}</strong></div><div className="research-metric"><span>Provider</span><strong>{market?.sourceName ?? position?.quote?.source ?? "N/A"}</strong></div></div></section>
3133 - <section><h3>Valuation</h3><div className="research-metric-grid"><StructuredMetric label="Market cap" research={research} fact="marketCap" /><StructuredMetric label="Enterprise value" research={research} fact="enterpriseValue" /><StructuredMetric label="Trailing P/E" research={research} fact="trailingPE" /><StructuredMetric label="Forward P/E" research={research} fact="forwardPE" /><StructuredMetric label="P/B" research={research} fact="priceToBook" /><StructuredMetric label="P/S" research={research} fact="priceToSales" /><StructuredMetric label="PEG" research={research} fact="pegRatio" />{!financial && !isEtf ? <><StructuredMetric label="EV/revenue" research={research} fact="evToRevenue" /><StructuredMetric label="EV/EBITDA" research={research} fact="evToEbitda" /></> : null}<StructuredMetric label="Trailing EPS" research={research} fact="trailingEps" /><StructuredMetric label="Forward EPS" research={research} fact="forwardEps" /></div><p><Badge tone={valuationTone(research?.valuation.state)}>{research?.valuation.state ?? "UNKNOWN"}</Badge> {research?.valuation.reason ?? "Awaiting contextual public research."}</p></section>
3134 - {!isEtf ? <section><h3>Quality / Fundamentals</h3><div className="research-metric-grid"><EvidenceMetric label="ROE" metric={structuredFact(research, "roe") ?? research?.valuation.roe} /><StructuredMetric label="ROA" research={research} fact="roa" />{!financial ? <EvidenceMetric label="ROCE" metric={structuredFact(research, "roce") ?? research?.valuation.roce} /> : null}<StructuredMetric label="Operating margin" research={research} fact="operatingMargin" /><StructuredMetric label="Net margin" research={research} fact="profitMargin" /><StructuredMetric label="Revenue growth" research={research} fact="revenueGrowth" /><StructuredMetric label="Earnings growth" research={research} fact="earningsGrowth" /><StructuredMetric label="Cash" research={research} fact="totalCash" /><StructuredMetric label="Debt" research={research} fact="totalDebt" /><StructuredMetric label="Debt / equity" research={research} fact="debtToEquity" /><StructuredMetric label="Free cash flow" research={research} fact="freeCashFlow" /><StructuredMetric label="Operating cash flow" research={research} fact="operatingCashFlow" /></div></section> : null}
3133 + <section><h3>Valuation</h3><div className="research-metric-grid"><StructuredMetric label="Market cap" research={research} fact="marketCap" /><StructuredMetric label="Enterprise value" research={research} fact="enterpriseValue" /><StructuredMetric label="Trailing P/E" research={research} fact="trailingPE" /><StructuredMetric label="Forward P/E" research={research} fact="forwardPE" /><StructuredMetric label="P/B" research={research} fact="priceToBook" /><StructuredMetric label="P/S" research={research} fact="priceToSales" /><StructuredMetric label="PEG" research={research} fact="pegRatio" />{!financial && !isEtf ? <><StructuredMetric label="EV/revenue" research={research} fact="evToRevenue" /><StructuredMetric label="EV/EBITDA" research={research} fact="evToEbitda" /></> : null}<StructuredMetric label="Trailing EPS" research={research} fact="trailingEps" /><StructuredMetric label="Forward EPS" research={research} fact="forwardEps" /></div><p><Badge tone={valuationTone(research?.valuation?.state ?? "UNKNOWN")}>{research?.valuation?.state ?? "UNKNOWN"}</Badge> {research?.valuation?.reason ?? "Awaiting contextual public research."}</p></section>
3134 + {!isEtf ? <section><h3>Quality / Fundamentals</h3><div className="research-metric-grid"><EvidenceMetric label="ROE" metric={structuredFact(research, "roe") ?? research?.valuation?.roe} /><StructuredMetric label="ROA" research={research} fact="roa" />{!financial ? <EvidenceMetric label="ROCE" metric={structuredFact(research, "roce") ?? research?.valuation?.roce} /> : null}<StructuredMetric label="Operating margin" research={research} fact="operatingMargin" /><StructuredMetric label="Net margin" research={research} fact="profitMargin" /><StructuredMetric label="Revenue growth" research={research} fact="revenueGrowth" /><StructuredMetric label="Earnings growth" research={research} fact="earningsGrowth" /><StructuredMetric label="Cash" research={research} fact="totalCash" /><StructuredMetric label="Debt" research={research} fact="totalDebt" /><StructuredMetric label="Debt / equity" research={research} fact="debtToEquity" /><StructuredMetric label="Free cash flow" research={research} fact="freeCashFlow" /><StructuredMetric label="Operating cash flow" research={research} fact="operatingCashFlow" /></div></section> : null}
3135 <section><h3>Analyst View</h3><p>External public analyst consensus; not an application recommendation.</p><div className="research-metric-grid"><div className="research-metric"><span>Current Price</span><strong>{currentPrice}</strong></div><StructuredMetric label="Target low" research={research} fact="publicAnalystTargetLowPrice" /><StructuredMetric label="Target median" research={research} fact="publicAnalystTargetMedianPrice" /><StructuredMetric label="Target mean" research={research} fact="publicAnalystTargetMeanPrice" /><StructuredMetric label="Target high" research={research} fact="publicAnalystTargetHighPrice" /><StructuredMetric label="Number of analysts" research={research} fact="publicAnalystCount" /><StructuredMetric label="Consensus" research={research} fact="publicAnalystConsensus" /><StructuredMetric label="Consensus score" research={research} fact="publicAnalystRecommendationMean" /></div></section>
3136 <section><h3>Latest Quarterly Result</h3>{result ? <><p>{result.documentTitle ?? "Quarterly financial result"} · {statementPeriod(result.period)} · {result.reportingBasis ?? "Reporting basis N/A"} · {result.resultDate ? statementPeriod(result.resultDate) : "Result date N/A"}</p><div className="research-metric-grid">{financial ? <><EvidenceMetric label="Total income" metric={result.revenue} /><EvidenceMetric label="PAT / net profit" metric={result.pat} /><EvidenceMetric label="EPS" metric={result.eps} /><EvidenceMetric label="NIM" metric={result.nim} /><EvidenceMetric label="ROA" metric={result.roa} /><EvidenceMetric label="ROE" metric={result.roe} /><EvidenceMetric label="Gross NPA" metric={result.grossNpa} /><EvidenceMetric label="Net NPA" metric={result.netNpa} /><EvidenceMetric label="Deposits" metric={result.deposits} /><EvidenceMetric label="Advances" metric={result.advances} /><EvidenceMetric label="Capital adequacy" metric={result.capitalAdequacy} /><EvidenceMetric label="Credit cost" metric={result.creditCost} /></> : <><EvidenceMetric label="Revenue" metric={result.revenue} /><EvidenceMetric label="Revenue YoY" metric={result.revenueYoYPercent} /><EvidenceMetric label="EBITDA / operating profit" metric={result.ebitda} /><EvidenceMetric label="EBITDA / operating margin" metric={result.ebitdaMargin} /><EvidenceMetric label="PAT / net profit" metric={result.pat} /><EvidenceMetric label="PAT YoY" metric={result.patYoYPercent} /><EvidenceMetric label="EPS" metric={result.eps} /><EvidenceMetric label="Debt / borrowings" metric={result.debtOrBorrowings} /></>}</div>{result.yoySummary ? <p>{result.yoySummary}</p> : null}<p>Source: {filingSourceLabel(result.sourceName)} · Published {result.publishedAt ? new Date(result.publishedAt).toLocaleDateString() : "N/A"} · Retrieved {new Date(result.retrievedAt).toLocaleString()}</p><a href={result.sourceUrl} target="_blank" rel="noreferrer">View {filingSourceLabel(result.sourceName)} Filing ↗</a></> : <p>{research?.quarterlyResultStatus === "PDF_SCANNED_OCR_REQUIRED" ? "PDF scanned; OCR required." : "Not publicly available."}</p>}</section>
3137 {!isEtf ? <section><h3>Financial History</h3><h4>Quarterly</h4><FinancialHistoryTable periods={(research?.financialResultHistory ?? []).filter((period) => period.periodType === "QUARTERLY").slice(0, 4)} /><h4>Annual</h4><FinancialHistoryTable periods={(research?.financialResultHistory ?? []).filter((period) => period.periodType === "ANNUAL")} /></section> : null}
@@ -3144,7 +3144,7 @@ function StockResearchDrawer({ position, watchlistItem, research, onUpdateDispla
3144 <DurableEvidenceSection title="Customers" evidence={research?.durableCategoryEvidence?.CLIENTS} />
3145 <DurableEvidenceSection title="Catalysts" evidence={research?.durableCategoryEvidence?.CATALYSTS} />
3146 <section><h3>News</h3>{market?.news?.length ? market.news.map((article) => <article key={article.url}><strong>{article.headline}</strong><p>{article.publisher} · {article.publishedAt ? new Date(article.publishedAt).toLocaleString() : "Date unavailable"}</p><a href={article.url} target="_blank" rel="noreferrer">Open source</a></article>) : <p>No public provider news available.</p>}</section>
3147 - <section><h3>Research & Evidence</h3><p>Status: {research?.status?.replaceAll("_", " ") ?? "Awaiting research"}; {research?.sourceDiversity.domainsFound ?? 0} unique domains, {research?.sourceDiversity.exchangeSources ?? 0} exchange sources, {research?.sourceDiversity.companySources ?? 0} company sources, {research?.sourceDiversity.secondarySources ?? 0} secondary sources.</p>{research?.latestEvent ? <a href={research.latestEvent.sourceUrl} target="_blank" rel="noreferrer">Latest evidence source</a> : null}</section>
3147 + <section><h3>Research & Evidence</h3><p>Status: {research?.status?.replaceAll("_", " ") ?? "Awaiting research"}; {research?.sourceDiversity?.domainsFound ?? 0} unique domains, {research?.sourceDiversity?.exchangeSources ?? 0} exchange sources, {research?.sourceDiversity?.companySources ?? 0} company sources, {research?.sourceDiversity?.secondarySources ?? 0} secondary sources.</p>{research?.latestEvent ? <a href={research.latestEvent.sourceUrl} target="_blank" rel="noreferrer">Latest evidence source</a> : null}</section>
3148 </aside>
3149 </div>
3150 );
@@ -3495,6 +3495,42 @@ function ResearchView({
3495 useEffect(() => {
3496 setResearchDetail(null);
3497 }, [researchContext.kind, selectedResearchInstrumentId]);
3498 + const [watchlistDetailResearch, setWatchlistDetailResearch] = useState<{
3499 + instrumentId: string;
3500 + research: PortfolioResearchCompany;
3501 + } | null>(null);
3502 + const [watchlistDetailError, setWatchlistDetailError] = useState<string | null>(null);
3503 + const [watchlistDetailLoading, setWatchlistDetailLoading] = useState(false);
3504 + const [watchlistDetailRetryKey, setWatchlistDetailRetryKey] = useState(0);
3505 + const watchlistDetailFetchId = useRef(0);
3506 + const detailRegion =
3507 + researchContext.kind === "WATCHLIST" ? researchContext.region : undefined;
3508 + useEffect(() => {
3509 + const detail = researchDetail;
3510 + const region = detailRegion;
3511 + if (!detail || !detail.watchlistInstrumentId || !region) {
3512 + setWatchlistDetailResearch(null);
3513 + setWatchlistDetailError(null);
3514 + setWatchlistDetailLoading(false);
3515 + return;
3516 + }
3517 + const fetchId = ++watchlistDetailFetchId.current;
3518 + const instrumentId = detail.researchInstrumentId;
3519 + setWatchlistDetailLoading(true);
3520 + setWatchlistDetailError(null);
3521 + researchApi
3522 + .getCompanyPresentation(instrumentId, region)
3523 + .then((research) => {
3524 + if (fetchId !== watchlistDetailFetchId.current) return;
3525 + setWatchlistDetailResearch({ instrumentId, research });
3526 + setWatchlistDetailLoading(false);
3527 + })
3528 + .catch((error) => {
3529 + if (fetchId !== watchlistDetailFetchId.current) return;
3530 + setWatchlistDetailError(getApiFailure(error).message);
3531 + setWatchlistDetailLoading(false);
3532 + });
3533 + }, [researchDetail?.watchlistInstrumentId, researchDetail?.researchInstrumentId, detailRegion, watchlistDetailRetryKey]);
3534 const filteredEvents = (summary?.recentEvents ?? []).filter((event) => {
3535 return (!eventType || event.eventType === eventType) && (!impact || event.impact === impact);
3536 });
@@ -3513,11 +3549,19 @@ function ResearchView({
3549 const detailWatchlistItem = researchDetail?.watchlistInstrumentId
3550 ? watchlistResearch?.instruments.find((item) => item.globalInstrumentId === researchDetail.watchlistInstrumentId)
3551 : undefined;
3516 - const detailResearch = researchDetail
3517 - ? (detailWatchlistItem?.company
3518 - ?? portfolioResearchSummary?.companies.find((company) => company.instrumentId === researchDetail.researchInstrumentId)
3519 - ?? (researchContext.kind === "SEARCH" ? searchPresentation ?? null : null))
3552 + const portfolioDetailResearch = researchDetail
3553 + ? portfolioResearchSummary?.companies.find(
3554 + (company) => company.instrumentId === researchDetail.researchInstrumentId
3555 + )
3556 : undefined;
3557 + const detailResearch: PortfolioResearchCompany | undefined = !researchDetail
3558 + ? undefined
3559 + : researchDetail.watchlistInstrumentId
3560 + ? watchlistDetailResearch?.instrumentId === researchDetail.researchInstrumentId
3561 + ? watchlistDetailResearch.research
3562 + : undefined
3563 + : portfolioDetailResearch
3564 + ?? (researchContext.kind === "SEARCH" ? searchPresentation ?? undefined : undefined);
3565 const researchOptions = useMemo(() => {
3566 if (researchContext.kind === "WATCHLIST") {
3567 return (watchlistResearch?.instruments ?? []).map((item) => ({
@@ -3648,6 +3692,33 @@ function ResearchView({
3692 </div>
3693 {researchContext.kind === "WATCHLIST" ? (watchlistResearch?.instruments ?? []).map((item) => {
3694 const company = item.company;
3695 + const companyStatus = company.status ?? "";
3696 +
3697 + const watchlistPrice =
3698 + item.marketData?.quote?.last?.amount
3699 + ?? item.marketData?.snapshot?.price
3700 + ?? null;
3701 +
3702 + const watchlistPe =
3703 + item.marketData?.snapshot?.peRatio
3704 + ?? null;
3705 +
3706 + const watchlistCurrency =
3707 + item.marketData?.quote?.currency
3708 + ?? item.marketData?.snapshot?.currency
3709 + ?? item.currency
3710 + ?? undefined;
3711 +
3712 + const valuationState = company.valuation?.state;
3713 + const valuationReason = company.valuation?.reason;
3714 +
3715 + const ownershipIncreases = company.ownershipIncreases ?? [];
3716 + const shareholdingChanges = company.shareholdingChanges ?? [];
3717 + const catalysts = company.currentQuarterCatalysts ?? [];
3718 +
3719 + const sourceCount = company.sourceCount;
3720 + const documentCount = company.documentCount;
3721 +
3722 const performanceTone = performanceRowTone(item.sourcePerformancePct);
3723 return (
3724 <button
@@ -3669,18 +3740,43 @@ function ResearchView({
3740 }}
3741 >
3742 <span role="cell">
3672 - <strong>{company.companyName} <span className={`research-status-dot research-status-${researchStatusTone(company.status)}`} role="img" aria-label={researchStatusDescription(company.status)} title={researchStatusDescription(company.status)} /></strong>
3743 + <strong>{company.companyName} <span className={`research-status-dot research-status-${researchStatusTone(companyStatus)}`} role="img" aria-label={researchStatusDescription(companyStatus)} title={researchStatusDescription(companyStatus)} /></strong>
3744 <small>{[company.ticker, company.exchange, company.isin].filter(Boolean).join(" / ")}</small>
3745 <small>{researchContext.name} · Public company research · Not held</small>
3746 </span>
3676 - <span role="cell">{company.currentPrice == null ? "—" : formatMoney(Number(company.currentPrice), company.structuredMarket?.resolution.currency ?? item.currency ?? undefined)}<small>P/E {metricText(company.valuation.currentPe)}</small></span>
3677 - <span role="cell"><Badge tone={valuationTone(company.valuation.state)}>{company.valuation.state}</Badge><small>{company.valuation.reason}</small></span>
3678 - <span role="cell">{latestResultText(company)}</span>
3679 - <span role="cell">{company.ownershipIncreases.length ? company.ownershipIncreases.map((value) => value === "FII_FPI" ? "FII/FPI" : value.replaceAll("_", " ")).join(" · ") : "Unavailable"}<small>{company.shareholdingChanges.length ? `${company.shareholdingChanges.length} comparable trends` : "Previous comparable period not found"}</small></span>
3680 - <span role="cell">{company.currentQuarterCatalysts.length ? `${company.currentQuarterCatalysts.length} current` : company.catalystScore ?? "None verified"}</span>
3681 - <span role="cell">{company.sourceCount} sources / {company.documentCount} docs</span>
3747 <span role="cell">
3683 - <Badge tone={researchStatusTone(company.status)}>{company.status.replaceAll("_", " ")}</Badge>
3748 + {watchlistPrice == null
3749 + ? "N/A"
3750 + : formatMoney(Number(watchlistPrice), watchlistCurrency)}
3751 + <small>
3752 + {watchlistPe == null
3753 + ? "P/E N/A"
3754 + : `P/E ${new Intl.NumberFormat("en-IN", {
3755 + maximumFractionDigits: 2
3756 + }).format(Number(watchlistPe))}`}
3757 + </small>
3758 + </span>
3759 + <span role="cell">
3760 + <Badge tone={valuationTone(valuationState)}>{valuationState ?? "N/A"}</Badge>
3761 + <small>{valuationReason ?? ""}</small>
3762 + </span>
3763 + <span role="cell">Not publicly available</span>
3764 + <span role="cell">
3765 + {ownershipIncreases.length
3766 + ? ownershipIncreases.map((value) => value === "FII_FPI" ? "FII/FPI" : value.replaceAll("_", " ")).join(" · ")
3767 + : "Unavailable"}
3768 + <small>{shareholdingChanges.length ? `${shareholdingChanges.length} comparable trends` : "Previous comparable period not found"}</small>
3769 + </span>
3770 + <span role="cell">
3771 + {catalysts.length ? `${catalysts.length} current` : company.catalystScore ?? "None verified"}
3772 + </span>
3773 + <span role="cell">
3774 + {sourceCount == null && documentCount == null
3775 + ? "N/A"
3776 + : `${sourceCount ?? 0} sources / ${documentCount ?? 0} docs`}
3777 + </span>
3778 + <span role="cell">
3779 + <Badge tone={researchStatusTone(companyStatus)}>{companyStatus.replaceAll("_", " ")}</Badge>
3780 {item.sourcePeriod ? <strong className={`market-intelligence-table-return ${performanceTone}-text`}>Market return ({item.sourcePeriod}) {formatSignedPerformancePct(item.sourcePerformancePct)}</strong> : null}
3781 </span>
3782 </button>
@@ -3735,7 +3831,23 @@ function ResearchView({
3831 : "Choose a Market Intelligence stock to add it to this regional watchlist."}
3832 />
3833 )}
3738 - {researchDetail && detailResearch ? <StockResearchDrawer position={researchDetail.position} watchlistItem={detailWatchlistItem} research={detailResearch} onClose={() => setResearchDetail(null)} /> : null}
3834 + {researchDetail && researchDetail.watchlistInstrumentId
3835 + ? watchlistDetailLoading
3836 + ? <>
3837 + <Skeleton rows={4} />
3838 + <p>Loading public research...</p>
3839 + </>
3840 + : watchlistDetailError
3841 + ? <ErrorState
3842 + message={watchlistDetailError}
3843 + action={<Button variant="secondary" onClick={() => setWatchlistDetailRetryKey((key) => key + 1)}>Retry</Button>}
3844 + />
3845 + : detailResearch
3846 + ? <StockResearchDrawer position={researchDetail.position} watchlistItem={detailWatchlistItem} research={detailResearch} onClose={() => setResearchDetail(null)} />
3847 + : null
3848 + : researchDetail && detailResearch
3849 + ? <StockResearchDrawer position={researchDetail.position} watchlistItem={detailWatchlistItem} research={detailResearch} onClose={() => setResearchDetail(null)} />
3850 + : null}
3851 </Card>
3852
3853 <Card className="wide-panel research-sticky-panel">
frontend/app/lib/portfolio-api.ts
+53
@@ -205,6 +205,7 @@ export type WatchlistResearchInstrument = {
205 sourcePeriod?: SectorPerformance["period"] | null;
206 sourcePerformancePct?: number | null;
207 addedAt?: string | null;
208 + marketData?: WatchlistMarketData | null;
209 company: PortfolioResearchCompany;
210 };
211 export type WatchlistResearchPresentation = {
@@ -273,6 +274,58 @@ export type Quote = {
274 receivedAt?: string;
275 };
276
277 +export type WatchlistSnapshot = {
278 + providerTicker?: string | null;
279 + exchange?: string | null;
280 + currency?: string | null;
281 + quoteType?: string | null;
282 + price?: number | null;
283 + priceCurrency?: string | null;
284 + marketAsOf?: string | null;
285 + retrievedAt?: string | null;
286 + source?: string | null;
287 + status?: string | null;
288 + previousClose?: number | null;
289 + bid?: number | null;
290 + ask?: number | null;
291 + volume?: number | null;
292 + averageVolume10Day?: number | null;
293 + averageVolume3Month?: number | null;
294 + fiftyTwoWeekHigh?: number | null;
295 + fiftyTwoWeekLow?: number | null;
296 + marketCap?: number | null;
297 + enterpriseValue?: number | null;
298 + peRatio?: number | null;
299 + forwardPe?: number | null;
300 + pbRatio?: number | null;
301 + psRatio?: number | null;
302 + pegRatio?: number | null;
303 + evEbitda?: number | null;
304 + trailingEps?: number | null;
305 + forwardEps?: number | null;
306 + roe?: number | null;
307 + roa?: number | null;
308 + roce?: number | null;
309 + netMargin?: number | null;
310 + operatingMargin?: number | null;
311 + debt?: number | null;
312 + cash?: number | null;
313 + debtEquity?: number | null;
314 + revenueGrowth?: number | null;
315 + earningsGrowth?: number | null;
316 + freeCashFlow?: number | null;
317 + operatingCashFlow?: number | null;
318 +};
319 +
320 +export type WatchlistMarketData = {
321 + canonicalName?: string | null;
322 + isin?: string | null;
323 + providerMappings?: Instrument["providerMappings"];
324 + snapshot?: WatchlistSnapshot | null;
325 + quote?: Quote | null;
326 + marketDataError?: string | null;
327 +};
328 +
329 export type PortfolioPosition = {
330 positionId: string;
331 portfolioId: string;