Closes #012 - Implement Sloan Ratio Accrual Radar

This commit is contained in:
Antigravity Agent
2026-06-13 12:56:52 +02:00
parent b94a7fcdd8
commit 0182bc22f0
8 changed files with 464 additions and 56 deletions

View File

@@ -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
};
})
);