135 lines
4.7 KiB
JavaScript
135 lines
4.7 KiB
JavaScript
import { Asset } from './Asset.js';
|
|
|
|
export class Stock extends Asset {
|
|
constructor(data) {
|
|
super(data);
|
|
// console.log('Data:', data);
|
|
this.sector = this._mapToStandardSector(data || {});
|
|
|
|
this.metrics = {
|
|
sector: this.sector,
|
|
// Valuation
|
|
peRatio: data.peRatio ?? null,
|
|
pegRatio: data.pegRatio ?? null,
|
|
priceToBook: data.priceToBook ?? null,
|
|
// Profitability
|
|
netProfitMargin: data.netProfitMargin ?? null,
|
|
operatingMargin: data.operatingMargin ?? null,
|
|
returnOnEquity: data.returnOnEquity ?? null,
|
|
// Growth
|
|
revenueGrowth: data.revenueGrowth ?? null,
|
|
earningsGrowth: data.earningsGrowth ?? null,
|
|
// Financial health
|
|
debtToEquity: data.debtToEquity ?? null,
|
|
quickRatio: data.quickRatio ?? null,
|
|
// Cash flow
|
|
fcfYield: data.fcfYield ?? null,
|
|
pFFO: data.pFFO ?? null,
|
|
// Income
|
|
dividendYield: data.dividendYield ?? null,
|
|
// Risk & momentum
|
|
beta: data.beta ?? null,
|
|
week52High: data.week52High ?? null,
|
|
week52Low: data.week52Low ?? null,
|
|
currentPrice: data.currentPrice ?? 0,
|
|
};
|
|
}
|
|
|
|
_mapToStandardSector(data) {
|
|
const profile = data.assetProfile || {};
|
|
const industry = (profile.industry || '').toLowerCase();
|
|
const sector = (profile.sector || '').toLowerCase();
|
|
const combined = `${industry} ${sector}`;
|
|
|
|
// Yahoo Finance sector/industry strings mapped to our internal sector constants.
|
|
// Order matters — more specific matches first.
|
|
if (
|
|
combined.includes('technology') ||
|
|
combined.includes('electronic') ||
|
|
combined.includes('semiconductor') ||
|
|
combined.includes('software')
|
|
)
|
|
return 'TECHNOLOGY';
|
|
if (combined.includes('real estate') || combined.includes('reit')) return 'REIT';
|
|
if (
|
|
combined.includes('financial') ||
|
|
combined.includes('bank') ||
|
|
combined.includes('insurance') ||
|
|
combined.includes('asset management')
|
|
)
|
|
return 'FINANCIAL';
|
|
if (
|
|
combined.includes('energy') ||
|
|
combined.includes('oil') ||
|
|
combined.includes('gas') ||
|
|
combined.includes('petroleum')
|
|
)
|
|
return 'ENERGY';
|
|
if (
|
|
combined.includes('health') ||
|
|
combined.includes('biotech') ||
|
|
combined.includes('pharmaceutical') ||
|
|
combined.includes('medical')
|
|
)
|
|
return 'HEALTHCARE';
|
|
// Yahoo calls this "Communication Services" — covers META, GOOGL, NFLX, DIS, T
|
|
if (
|
|
combined.includes('communication') ||
|
|
combined.includes('media') ||
|
|
combined.includes('entertainment') ||
|
|
combined.includes('telecom')
|
|
)
|
|
return 'COMMUNICATION';
|
|
if (
|
|
combined.includes('consumer defensive') ||
|
|
combined.includes('consumer staples') ||
|
|
combined.includes('household') ||
|
|
combined.includes('beverage') ||
|
|
combined.includes('food')
|
|
)
|
|
return 'CONSUMER_STAPLES';
|
|
if (
|
|
combined.includes('consumer cyclical') ||
|
|
combined.includes('consumer discretionary') ||
|
|
combined.includes('retail') ||
|
|
combined.includes('apparel') ||
|
|
combined.includes('auto')
|
|
)
|
|
return 'CONSUMER_DISCRETIONARY';
|
|
|
|
return 'GENERAL';
|
|
}
|
|
|
|
getDisplayMetrics() {
|
|
const fmt = (v, dec = 1, suffix = '') => (v != null ? `${v.toFixed(dec)}${suffix}` : null);
|
|
const m = this.metrics;
|
|
const w52pos =
|
|
m.week52High > 0 && m.week52Low != null && m.currentPrice > 0
|
|
? (((m.currentPrice - m.week52Low) / (m.week52High - m.week52Low)) * 100).toFixed(0) + '%'
|
|
: null;
|
|
|
|
// Only include fields that have actual data — null fields are omitted
|
|
const display = {
|
|
Ticker: this.ticker,
|
|
Price: this.formatCurrency(this.currentPrice),
|
|
Sector: this.sector,
|
|
};
|
|
if (m.peRatio != null) display['P/E'] = fmt(m.peRatio, 1);
|
|
if (m.pegRatio != null) display['PEG'] = fmt(m.pegRatio, 2);
|
|
if (m.priceToBook != null) display['P/B'] = fmt(m.priceToBook, 2);
|
|
if (m.returnOnEquity != null) display['ROE%'] = fmt(m.returnOnEquity, 1, '%');
|
|
if (m.operatingMargin != null) display['OpMgn%'] = fmt(m.operatingMargin, 1, '%');
|
|
if (m.netProfitMargin != null) display['NetMgn%'] = fmt(m.netProfitMargin, 1, '%');
|
|
if (m.revenueGrowth != null) display['Rev%'] = fmt(m.revenueGrowth, 1, '%');
|
|
if (m.fcfYield != null) display['FCF Yld%'] = fmt(m.fcfYield, 1, '%');
|
|
if (m.dividendYield != null) display['Div%'] = fmt(m.dividendYield, 2, '%');
|
|
if (m.debtToEquity != null) display['D/E'] = fmt(m.debtToEquity, 2);
|
|
if (m.quickRatio != null) display['Quick'] = fmt(m.quickRatio, 2);
|
|
if (m.beta != null) display['Beta'] = fmt(m.beta, 2);
|
|
if (w52pos != null) display['52W Pos'] = w52pos;
|
|
if (m.pFFO != null) display['P/FFO'] = fmt(m.pFFO, 1);
|
|
|
|
return display;
|
|
}
|
|
}
|