initial commit
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
node_modules
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# Market News Analysis & Catalyst Screener
|
||||||
|
|
||||||
|
## 1. High-Alpha Catalyst Analysis Prompt
|
||||||
|
|
||||||
|
Copy and paste this into your LLM daily to filter noise into actionable data:
|
||||||
|
|
||||||
|
> **Role:** You are a Quant-driven Financial Analyst specialized in Catalyst-Driven Trading.
|
||||||
|
>
|
||||||
|
> **Task:** Analyze today’s top 3 high-impact news stories and map them to the specific assets that are structurally forced to respond.
|
||||||
|
>
|
||||||
|
> **Instructions:**
|
||||||
|
>
|
||||||
|
> 1. **Identify the Catalyst:** Select one Macro event, one Sector-wide (regulatory/supply-chain) shift, and one Company-specific surprise.
|
||||||
|
> 2. **Correlation Logic:** For each catalyst, identify:
|
||||||
|
> - **Primary Target:** The ticker directly mentioned.
|
||||||
|
> - **Ripple-Effect Target:** A ticker in the supply chain or direct competitor (The "Alpha" play).
|
||||||
|
> 3. **Quantitative Impact Matrix:** Produce a table with: `Catalyst` | `Tickers (Primary/Ripple)` | `Bias` | `Sensitivity (1-5)` | `Mechanics`.
|
||||||
|
> 4. **Constraint:** Exclude "Market Sentiment" or generic analyst upgrades. Only include events with a measurable impact on valuation or supply chain fundamentals.
|
||||||
|
> 5. **Liquidity Filter:** Do not suggest tickers with daily volume below 500k.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Quantitative Impact Matrix (Template)
|
||||||
|
|
||||||
|
Use this table to log the results from the prompt above:
|
||||||
|
|
||||||
|
| Catalyst | Tickers (Primary / Ripple) | Bias | Sensitivity (1-5) | Mechanics |
|
||||||
|
| :----------- | :------------------------- | :-------- | :---------------- | :------------------------ |
|
||||||
|
| [Event Name] | [Ticker1] / [Ticker2] | Bull/Bear | [1-5] | [Concise financial logic] |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Implementation Workflow
|
||||||
|
|
||||||
|
1. **Fetch:** Run the prompt above using live news sources (e.g., Bloomberg, Nasdaq, Briefing.com).
|
||||||
|
2. **Screen:** Plug the resulting tickers into your `ScreenerEngine.js`.
|
||||||
|
3. **Validate:** Use your "Verdict Justification" table to verify if the fundamentals (PEG, Margins, Debt) support the AI's suggested bias.
|
||||||
|
4. **Execute:** Monitor the "Ripple-Effect" targets, as they often capture volatility before the broader market catches on.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. June 2026 Focus Areas
|
||||||
|
|
||||||
|
- **Macro:** Watch the ISM Manufacturing PMI (June 1) and Nonfarm Payrolls (June 5).
|
||||||
|
- **Geopolitical:** Monitor US-Iran negotiations regarding the Strait of Hormuz (impacts Oil/Energy supply chains).
|
||||||
|
- **Sectoral:** Continued AI momentum—look for infrastructure and cybersecurity earnings/guidance.
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { ScreenerEngine } from './src/core/ScreenerEngine.js';
|
||||||
|
|
||||||
|
const tickers = [
|
||||||
|
'PLTR',
|
||||||
|
'AAPL',
|
||||||
|
'VOO',
|
||||||
|
'MSFT',
|
||||||
|
'TSLA',
|
||||||
|
'QQQ',
|
||||||
|
'O',
|
||||||
|
'BND',
|
||||||
|
'AGG',
|
||||||
|
'LQD',
|
||||||
|
'GOVT',
|
||||||
|
'MUB',
|
||||||
|
'SHY',
|
||||||
|
'IEF',
|
||||||
|
'TLT',
|
||||||
|
];
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('🚀 Starting Screener Evaluation...');
|
||||||
|
const engine = new ScreenerEngine();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await engine.runParallelScreener(tickers);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('\n Execution Failed:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error);
|
||||||
Generated
+1439
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "stock-screener",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node index.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"yahoo-finance2": "^3.15.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{ "holdings": [] }
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// src/api/YahooClient.js
|
||||||
|
import YahooFinance from 'yahoo-finance2';
|
||||||
|
|
||||||
|
export class YahooClient {
|
||||||
|
constructor() {
|
||||||
|
// Instantiate the client as required by v3
|
||||||
|
this.yf = new YahooFinance();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchSummary(ticker, retries = 3, backoff = 1000) {
|
||||||
|
for (let i = 0; i < retries; i++) {
|
||||||
|
try {
|
||||||
|
// Use the instance (this.yf) instead of the static import
|
||||||
|
return await this.yf.quoteSummary(ticker, {
|
||||||
|
modules: [
|
||||||
|
'assetProfile',
|
||||||
|
'financialData',
|
||||||
|
'defaultKeyStatistics',
|
||||||
|
'price',
|
||||||
|
'summaryDetail',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (i === retries - 1) throw error;
|
||||||
|
await new Promise((res) => setTimeout(res, backoff * (i + 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// src/core/Asset.js
|
||||||
|
export class Asset {
|
||||||
|
constructor(data) {
|
||||||
|
this.ticker = (data.ticker || 'UNKNOWN').toUpperCase();
|
||||||
|
this.currentPrice = data.currentPrice || 0;
|
||||||
|
this.type = data.type || 'stock';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: Format currency safely
|
||||||
|
formatCurrency(val) {
|
||||||
|
return val ? `$${val.toFixed(2)}` : 'N/A';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shared Logic: Generate the verdict score string
|
||||||
|
calculateVerdict(red, orange, green) {
|
||||||
|
if (red > 0) return '🔴 REJECT';
|
||||||
|
if (orange >= 3) return '🟡 WATCHLIST';
|
||||||
|
return '🟢 BUY';
|
||||||
|
}
|
||||||
|
|
||||||
|
formatLargeNumber(num) {
|
||||||
|
if (num >= 1e12) return (num / 1e12).toFixed(2) + 'T';
|
||||||
|
if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B';
|
||||||
|
if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M';
|
||||||
|
return num.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluate() {
|
||||||
|
throw new Error("Method 'evaluate()' must be implemented by subclass.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { Asset } from './Asset.js';
|
||||||
|
|
||||||
|
export class Bond extends Asset {
|
||||||
|
constructor({ yieldToMaturity, duration, creditRating, ...rest }) {
|
||||||
|
super(rest);
|
||||||
|
this.yieldToMaturity = yieldToMaturity ?? 0;
|
||||||
|
this.duration = duration ?? 0;
|
||||||
|
this.creditRating = creditRating ?? 'N/A';
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluate() {
|
||||||
|
let green = 0,
|
||||||
|
orange = 0,
|
||||||
|
red = 0;
|
||||||
|
|
||||||
|
// Custom Bond Rules
|
||||||
|
if (this.yieldToMaturity > 4.5) green++;
|
||||||
|
else orange++;
|
||||||
|
if (this.duration < 5) green++;
|
||||||
|
else red++;
|
||||||
|
|
||||||
|
const isStable = this.duration < 7; // < 7 years is generally lower interest-rate risk
|
||||||
|
|
||||||
|
return {
|
||||||
|
Ticker: this.ticker,
|
||||||
|
Type: 'BOND',
|
||||||
|
Price: this.formatCurrency(this.currentPrice),
|
||||||
|
'YTM%': `${this.yieldToMaturity}%`,
|
||||||
|
Duration: this.duration,
|
||||||
|
Rating: this.creditRating,
|
||||||
|
'G/O/R': `${green}/${orange}/${red}`,
|
||||||
|
Verdict: isStable ? '🟢 Stable' : '⚠️ Rate Sensitive',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { Asset } from './Asset.js';
|
||||||
|
|
||||||
|
export class Etf extends Asset {
|
||||||
|
constructor(data) {
|
||||||
|
super(data);
|
||||||
|
this.expenseRatio = data.expenseRatio ?? 0;
|
||||||
|
this.totalAssets = data.totalAssets ?? 0;
|
||||||
|
this.yield = data.yield ?? 0;
|
||||||
|
this.fiveYearReturn = data.fiveYearReturn ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluate() {
|
||||||
|
let green = 0,
|
||||||
|
orange = 0,
|
||||||
|
red = 0;
|
||||||
|
|
||||||
|
// Rule 1: Expense Ratio
|
||||||
|
if (this.expenseRatio !== null) {
|
||||||
|
this.expenseRatio <= 0.15 ? green++ : red++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule 2: Total Assets (size)
|
||||||
|
if (this.totalAssets > 0) {
|
||||||
|
this.totalAssets >= 1_000_000_000 ? green++ : red++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule 3: Yield
|
||||||
|
if (this.yield !== null) green++;
|
||||||
|
|
||||||
|
const isEfficient = this.expenseRatio < 0.2;
|
||||||
|
|
||||||
|
return {
|
||||||
|
Type: 'ETF',
|
||||||
|
Ticker: this.ticker,
|
||||||
|
Price: this.formatCurrency(this.currentPrice),
|
||||||
|
// Use optional chaining (?.) and nullish coalescing (?? 0)
|
||||||
|
'Exp Ratio%': `${(this.expenseRatio ?? 0).toFixed(2)}%`,
|
||||||
|
'Yield%': `${(this.yield ?? 0).toFixed(2)}%`,
|
||||||
|
AUM: this.formatLargeNumber(this.totalAssets ?? 0),
|
||||||
|
'5Y Return%': `${(this.fiveYearReturn ?? 0).toFixed(1)}%`,
|
||||||
|
Verdict: isEfficient ? '🟢 Efficient' : '🔴 High Cost', // Simplified for testing
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getJustification() {
|
||||||
|
const reasons = [];
|
||||||
|
if (this.expenseRatio > 0.15)
|
||||||
|
reasons.push(`High Fee (${this.expenseRatio}%)`);
|
||||||
|
if (this.totalAssets < 1_000_000_000) reasons.push(`Low AUM`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
Ticker: this.ticker,
|
||||||
|
Verdict: red > 0 ? 'Avoid' : 'Core Hold',
|
||||||
|
Reasoning: reasons.length > 0 ? reasons.join(', ') : 'Solid Foundation',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { YahooClient } from '../api/YahooClient.js';
|
||||||
|
import { mapToStandardFormat } from '../utils/DataMapper.js';
|
||||||
|
import { Stock } from './Stock.js';
|
||||||
|
import { Etf } from './Etf.js';
|
||||||
|
import { Bond } from './Bond.js';
|
||||||
|
import { chunkArray } from '../utils/Chunker.js';
|
||||||
|
|
||||||
|
export class ScreenerEngine {
|
||||||
|
constructor() {
|
||||||
|
this.client = new YahooClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
_createAssetInstance(data) {
|
||||||
|
const type = (data.type || 'STOCK').toUpperCase();
|
||||||
|
switch (type) {
|
||||||
|
case 'BOND':
|
||||||
|
return new Bond(data);
|
||||||
|
case 'ETF':
|
||||||
|
return new Etf(data);
|
||||||
|
default:
|
||||||
|
return new Stock(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _fetchAndProcess(ticker) {
|
||||||
|
try {
|
||||||
|
const summary = await this.client.fetchSummary(ticker);
|
||||||
|
if (!summary?.price) throw new Error('Invalid Payload');
|
||||||
|
return mapToStandardFormat(ticker, summary);
|
||||||
|
} catch (error) {
|
||||||
|
// Return a structured error object that mimics the successful data format
|
||||||
|
return {
|
||||||
|
isError: true,
|
||||||
|
Ticker: ticker.toUpperCase(),
|
||||||
|
Type: 'STOCK',
|
||||||
|
Verdict: `🔴 ${error.message}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async runParallelScreener(tickerList) {
|
||||||
|
const chunks = chunkArray(tickerList, 5);
|
||||||
|
const results = { STOCK: [], ETF: [], BOND: [] };
|
||||||
|
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
console.log(`🚀 Processing batch: ${chunk.join(', ')}`);
|
||||||
|
const rawDataBatch = await Promise.all(
|
||||||
|
chunk.map((t) => this._fetchAndProcess(t)),
|
||||||
|
);
|
||||||
|
|
||||||
|
rawDataBatch.forEach((data) => {
|
||||||
|
if (data.isError) {
|
||||||
|
results.STOCK.push(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const asset = this._createAssetInstance(data);
|
||||||
|
const evaluated = asset.evaluate();
|
||||||
|
|
||||||
|
// --- THE FIX ---
|
||||||
|
// If the evaluated.Type doesn't match a bucket,
|
||||||
|
// this console.warn will tell us exactly what key is missing.
|
||||||
|
const category = (evaluated.Type || data.type || 'STOCK').toUpperCase();
|
||||||
|
|
||||||
|
if (results[category]) {
|
||||||
|
results[category].push(evaluated);
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
`WARNING: Data dropped! Ticker ${data.ticker} has Type "${category}" which doesn't match results keys.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
this._display(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
_display(results) {
|
||||||
|
console.log('\n--- EQUITY MATRIX ---\n');
|
||||||
|
console.table(results.STOCK);
|
||||||
|
console.log('\n--- ETF MATRIX ---\n');
|
||||||
|
console.table(results.ETF);
|
||||||
|
console.log('\n--- BOND MATRIX ---\n');
|
||||||
|
console.table(results.BOND);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import { Asset } from './Asset.js';
|
||||||
|
|
||||||
|
export class Stock extends Asset {
|
||||||
|
constructor(data) {
|
||||||
|
super(data);
|
||||||
|
this.summaryData = data.summaryData;
|
||||||
|
this.industry = data.industry || this._detectIndustryType(data.summaryData);
|
||||||
|
|
||||||
|
// Financial Metrics
|
||||||
|
this.quickRatio = data.quickRatio ?? null;
|
||||||
|
this.debtToEquity = data.debtToEquity ?? 0;
|
||||||
|
this.fcfGrowth = data.fcfGrowth ?? 'neutral';
|
||||||
|
this.revenueGrowth = data.revenueGrowth ?? 0;
|
||||||
|
this.netProfitMargin = data.netProfitMargin ?? 0;
|
||||||
|
this.pegRatio = data.pegRatio ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_detectIndustryType(summary = {}) {
|
||||||
|
const profile = summary.assetProfile || {};
|
||||||
|
const industry = (profile.industry || '').toLowerCase();
|
||||||
|
const grossMargin = (summary.financialData?.grossMargins ?? 0) * 100;
|
||||||
|
const marketCap = summary.price?.marketCap || 0;
|
||||||
|
|
||||||
|
if (
|
||||||
|
grossMargin > 70 ||
|
||||||
|
industry.includes('software') ||
|
||||||
|
industry.includes('cloud')
|
||||||
|
)
|
||||||
|
return 'SaaS';
|
||||||
|
if (marketCap > 100_000_000_000) return 'Mega-Cap';
|
||||||
|
if (['telecom', 'utility', 'railroad'].some((i) => industry.includes(i)))
|
||||||
|
return 'Capital-Heavy';
|
||||||
|
|
||||||
|
return 'General';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extracted scoring rules for cleaner 'evaluate' method
|
||||||
|
_scoreMetric(value, thresholds, isGreen, isOrange) {
|
||||||
|
if (value === null || value === undefined) return 0; // Neutral
|
||||||
|
if (isGreen(value)) return 1; // Green
|
||||||
|
if (isOrange(value)) return -1; // Orange
|
||||||
|
return -2; // Red
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluate() {
|
||||||
|
let green = 0,
|
||||||
|
orange = 0,
|
||||||
|
red = 0;
|
||||||
|
|
||||||
|
const metrics = [
|
||||||
|
{
|
||||||
|
val: this.quickRatio,
|
||||||
|
green: (v) =>
|
||||||
|
v > 1.0 ||
|
||||||
|
((this.industry === 'SaaS' || this.industry === 'Mega-Cap') &&
|
||||||
|
v >= 0.7),
|
||||||
|
orange: (v) => v >= 0.7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val: this.debtToEquity,
|
||||||
|
green: (v) => v < 1.0,
|
||||||
|
orange: (v) => v <= 2.5 || this.industry === 'Capital-Heavy',
|
||||||
|
},
|
||||||
|
{ val: this.revenueGrowth, green: (v) => v > 10, orange: (v) => v >= 2 },
|
||||||
|
{
|
||||||
|
val: this.netProfitMargin,
|
||||||
|
green: (v) => v > 15 || this.industry === 'Retail',
|
||||||
|
orange: (v) => v >= 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val: this.pegRatio,
|
||||||
|
green: (v) => v > 0 && v <= 1.3,
|
||||||
|
orange: (v) => v <= 3.5 || ['SaaS', 'Mega-Cap'].includes(this.industry),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
metrics.forEach((m) => {
|
||||||
|
const score = this._scoreMetric(m.val, null, m.green, m.orange);
|
||||||
|
if (score === 1) green++;
|
||||||
|
else if (score === -1) orange++;
|
||||||
|
else if (m.val !== null) red++;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.fcfGrowth === 'positive') green++;
|
||||||
|
else if (['SaaS', 'Mega-Cap'].includes(this.industry)) orange++;
|
||||||
|
else red++;
|
||||||
|
|
||||||
|
const verdict = this.calculateVerdict(red, orange, green);
|
||||||
|
|
||||||
|
return {
|
||||||
|
Ticker: this.ticker,
|
||||||
|
Type: 'STOCK',
|
||||||
|
Price: this.formatCurrency(this.currentPrice),
|
||||||
|
'PEG/Fee': this.pegRatio?.toFixed(2) ?? 'N/A',
|
||||||
|
'Rev%': `${this.revenueGrowth.toFixed(1)}%`,
|
||||||
|
'Marg%': `${this.netProfitMargin.toFixed(1)}%`,
|
||||||
|
Quick: this.quickRatio?.toFixed(2) ?? 'N/A',
|
||||||
|
'D/E': this.debtToEquity.toFixed(2),
|
||||||
|
'G/O/R': `${green}/${orange}/${red}`,
|
||||||
|
Verdict: verdict,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export const chunkArray = (array, size) => {
|
||||||
|
const result = [];
|
||||||
|
for (let i = 0; i < array.length; i += size) {
|
||||||
|
result.push(array.slice(i, i + size));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
// src/utils/DataMapper.js
|
||||||
|
|
||||||
|
export const mapToStandardFormat = (ticker, summary) => {
|
||||||
|
const quoteType = summary.price?.quoteType;
|
||||||
|
const category = (summary.assetProfile?.category || '').toLowerCase();
|
||||||
|
const yieldVal = summary.summaryDetail?.trailingAnnualDividendYield ?? 0;
|
||||||
|
// Logic to determine type
|
||||||
|
const isBond =
|
||||||
|
category.includes('bond') ||
|
||||||
|
category.includes('fixed income') ||
|
||||||
|
category.includes('treasury') ||
|
||||||
|
(quoteType === 'ETF' && yieldVal > 0.02 && category === ''); // Heuristic fallback
|
||||||
|
if (quoteType === 'ETF') {
|
||||||
|
return isBond
|
||||||
|
? {
|
||||||
|
type: 'BOND',
|
||||||
|
ticker,
|
||||||
|
...mapBondData(summary),
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'ETF',
|
||||||
|
ticker,
|
||||||
|
...mapEtfData(summary),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to STOCK (covers 'EQUITY' or missing types)
|
||||||
|
return {
|
||||||
|
type: 'STOCK',
|
||||||
|
ticker,
|
||||||
|
...mapStockData(summary),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStockData = (summary) => ({
|
||||||
|
quickRatio: summary.financialData?.quickRatio ?? 0,
|
||||||
|
debtToEquity: (summary.financialData?.debtToEquity ?? 0) / 100,
|
||||||
|
fcfGrowth:
|
||||||
|
(summary.financialData?.freeCashflow ?? 0) > 0 ? 'positive' : 'negative',
|
||||||
|
revenueGrowth: (summary.financialData?.revenueGrowth ?? 0) * 100,
|
||||||
|
netProfitMargin: (summary.financialData?.profitMargins ?? 0) * 100,
|
||||||
|
pegRatio: summary.defaultKeyStatistics?.pegRatio ?? 0,
|
||||||
|
currentPrice: summary.price?.regularMarketPrice ?? 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapEtfData = (summary) => ({
|
||||||
|
expenseRatio: (summary.summaryDetail?.expenseRatio ?? 0) * 100,
|
||||||
|
totalAssets: summary.summaryDetail?.totalAssets ?? 0,
|
||||||
|
yield: (summary.summaryDetail?.trailingAnnualDividendYield ?? 0) * 100,
|
||||||
|
fiveYearReturn: summary.defaultKeyStatistics?.fiveYearAverageReturn ?? 0,
|
||||||
|
currentPrice: summary.price?.regularMarketPrice ?? 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapBondData = (summary) => ({
|
||||||
|
yieldToMaturity: (summary.summaryDetail?.yield ?? 0) * 100,
|
||||||
|
duration: summary.defaultKeyStatistics?.fiveYearAverageReturn ?? 0,
|
||||||
|
creditRating: summary.assetProfile?.governanceEpochDate ? 'Rated' : 'N/A',
|
||||||
|
currentPrice: summary.price?.regularMarketPrice ?? 0,
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user