Closes #012 - Implement Sloan Ratio Accrual Radar
This commit is contained in:
@@ -300,6 +300,61 @@ function generateSimulatedChart(ticker: string, mode: string): TickerDetails {
|
||||
};
|
||||
}
|
||||
|
||||
function getSimulatedSloan(ticker: string): { sloanRatio: number; sloanRegime: 'SAFE' | 'ANOMALY' } {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < ticker.length; i++) {
|
||||
hash = ticker.charCodeAt(i) + ((hash << 5) - hash);
|
||||
}
|
||||
const sloanRatio = parseFloat(((hash % 170) / 10).toFixed(2)); // range: 0.0 to 17.0
|
||||
const sloanRegime = (sloanRatio > 10 || sloanRatio < -10) ? ('ANOMALY' as const) : ('SAFE' as const);
|
||||
return { sloanRatio, sloanRegime };
|
||||
}
|
||||
|
||||
async function fetchFmpSloanRatio(ticker: string, apiKey: string): Promise<{ sloanRatio: number; sloanRegime: 'SAFE' | 'ANOMALY' }> {
|
||||
if (ticker.includes('-USD') || ticker.includes('BTC') || ticker.includes('ETH')) {
|
||||
return getSimulatedSloan(ticker);
|
||||
}
|
||||
try {
|
||||
const incUrl = `https://financialmodelingprep.com/api/v3/income-statement/${ticker}?period=quarter&limit=1&apikey=${apiKey}`;
|
||||
const balUrl = `https://financialmodelingprep.com/api/v3/balance-sheet-statement/${ticker}?period=quarter&limit=1&apikey=${apiKey}`;
|
||||
const cfUrl = `https://financialmodelingprep.com/api/v3/cash-flow-statement/${ticker}?period=quarter&limit=1&apikey=${apiKey}`;
|
||||
|
||||
const [incRes, balRes, cfRes] = await Promise.all([
|
||||
fetch(incUrl, { signal: AbortSignal.timeout(2000) }),
|
||||
fetch(balUrl, { signal: AbortSignal.timeout(2000) }),
|
||||
fetch(cfUrl, { signal: AbortSignal.timeout(2000) })
|
||||
]);
|
||||
|
||||
if (incRes.ok && balRes.ok && cfRes.ok) {
|
||||
const incData = await incRes.json();
|
||||
const balData = await balRes.json();
|
||||
const cfData = await cfRes.json();
|
||||
|
||||
const inc = incData?.[0] || {};
|
||||
const bal = balData?.[0] || {};
|
||||
const cf = cfData?.[0] || {};
|
||||
|
||||
const netIncome = inc.netIncome || 0;
|
||||
const cfo = cf.netCashProvidedByOperatingActivities || cf.operatingCashFlow || 0;
|
||||
const cfi = cf.netCashUsedForInvestingActivites || cf.netCashUsedForInvestingActivities || cf.investingCashFlow || 0;
|
||||
const totalAssets = bal.totalAssets || 0;
|
||||
|
||||
const accruals = netIncome - (cfo + cfi);
|
||||
const sloanRatio = totalAssets > 0 ? (accruals / totalAssets) * 100 : 0;
|
||||
const sloanRegime = (sloanRatio > 10 || sloanRatio < -10) ? ('ANOMALY' as const) : ('SAFE' as const);
|
||||
|
||||
return {
|
||||
sloanRatio: Number(sloanRatio.toFixed(2)),
|
||||
sloanRegime
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`Error fetching FMP Sloan data for ${ticker}:`, err);
|
||||
}
|
||||
|
||||
return getSimulatedSloan(ticker);
|
||||
}
|
||||
|
||||
async function fetchFMPFundamentalData(ticker: string, apiKey: string) {
|
||||
try {
|
||||
const profileUrl = `https://financialmodelingprep.com/stable/profile?symbol=${ticker}&apikey=${apiKey}`;
|
||||
@@ -426,10 +481,15 @@ export async function GET(request: Request) {
|
||||
? getMockFundamentals(item.ticker)
|
||||
: await fetchFMPFundamentalData(item.ticker, fmpApiKey);
|
||||
|
||||
const sloan = useMock
|
||||
? getSimulatedSloan(item.ticker)
|
||||
: await fetchFmpSloanRatio(item.ticker, fmpApiKey);
|
||||
|
||||
return {
|
||||
...item,
|
||||
...fund,
|
||||
dividendYield: fund.dividendYield ? Number((fund.dividendYield * (useMock ? 100 : 1)).toFixed(2)) : 0
|
||||
dividendYield: fund.dividendYield ? Number((fund.dividendYield * (useMock ? 100 : 1)).toFixed(2)) : 0,
|
||||
...sloan
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user