fin-ratios 0.7.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fetchers/polygon/index.cjs +176 -0
- package/dist/fetchers/polygon/index.cjs.map +1 -0
- package/dist/fetchers/polygon/index.d.cts +84 -0
- package/dist/fetchers/polygon/index.d.ts +84 -0
- package/dist/fetchers/polygon/index.js +173 -0
- package/dist/fetchers/polygon/index.js.map +1 -0
- package/dist/index.cjs +557 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +289 -1
- package/dist/index.d.ts +289 -1
- package/dist/index.js +553 -7
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/fetchers/polygon/index.ts
|
|
4
|
+
var POLYGON_BASE = "https://api.polygon.io/vX/reference/financials";
|
|
5
|
+
var _moduleKey;
|
|
6
|
+
function setPolygonApiKey(key) {
|
|
7
|
+
_moduleKey = key;
|
|
8
|
+
}
|
|
9
|
+
async function fetchPolygon(ticker, options = {}) {
|
|
10
|
+
const { numYears = 5, timeframe = "annual" } = options;
|
|
11
|
+
const apiKey = options.apiKey ?? _moduleKey ?? (typeof process !== "undefined" ? process.env["POLYGON_API_KEY"] : void 0);
|
|
12
|
+
if (!apiKey) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
"Polygon.io API key is required. Provide it via options.apiKey, setPolygonApiKey(), or the POLYGON_API_KEY environment variable."
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
const startDate = _startDate(Math.max(numYears, 2));
|
|
18
|
+
const params = new URLSearchParams({
|
|
19
|
+
ticker: ticker.toUpperCase(),
|
|
20
|
+
timeframe,
|
|
21
|
+
limit: String(Math.min(numYears, 100)),
|
|
22
|
+
sort: "period_of_report_date",
|
|
23
|
+
order: "asc",
|
|
24
|
+
"filing_date.gte": startDate,
|
|
25
|
+
apiKey
|
|
26
|
+
});
|
|
27
|
+
const url = `${POLYGON_BASE}?${params.toString()}`;
|
|
28
|
+
let payload;
|
|
29
|
+
try {
|
|
30
|
+
const resp = await fetch(url);
|
|
31
|
+
if (resp.status === 401) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
"Polygon API returned 401 Unauthorized \u2014 check your API key."
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
if (resp.status === 429) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
"Polygon API rate limit exceeded (free tier: 5 req/min). Wait a moment and retry."
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (!resp.ok) {
|
|
42
|
+
const body = await resp.text().catch(() => "");
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Polygon API request failed for ${ticker}: HTTP ${resp.status} \u2014 ${body.slice(0, 200)}`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
payload = await resp.json();
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (err instanceof Error && err.message.startsWith("Polygon API")) throw err;
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Polygon API request failed for ${ticker}: ${err instanceof Error ? err.message : String(err)}`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
const results = payload.results ?? [];
|
|
55
|
+
return results.map((item) => _mapRecord(item));
|
|
56
|
+
}
|
|
57
|
+
function _mapRecord(item) {
|
|
58
|
+
const inc = item.financials?.income_statement ?? {};
|
|
59
|
+
const bal = item.financials?.balance_sheet ?? {};
|
|
60
|
+
const cf = item.financials?.cash_flow_statement ?? {};
|
|
61
|
+
const revenue = _v(inc, "revenues", "net_revenues", "total_revenues");
|
|
62
|
+
const grossProfit = _v(inc, "gross_profit");
|
|
63
|
+
const ebit = _v(
|
|
64
|
+
inc,
|
|
65
|
+
"operating_income",
|
|
66
|
+
"income_loss_from_continuing_operations_before_tax"
|
|
67
|
+
);
|
|
68
|
+
const netIncome = _v(inc, "net_income_loss", "net_income_loss_attributable_to_parent");
|
|
69
|
+
const taxExpense = _v(inc, "income_tax_expense_benefit");
|
|
70
|
+
const interestExp = Math.abs(_v(inc, "interest_expense_operating", "interest_expense"));
|
|
71
|
+
const ebtDirect = _v(
|
|
72
|
+
inc,
|
|
73
|
+
"income_loss_from_continuing_operations_before_tax",
|
|
74
|
+
"income_before_income_taxes"
|
|
75
|
+
);
|
|
76
|
+
const ebt = ebtDirect !== 0 ? ebtDirect : netIncome + taxExpense;
|
|
77
|
+
const totalAssets = _v(bal, "assets");
|
|
78
|
+
const currentAssets = _v(bal, "current_assets");
|
|
79
|
+
const cash = _v(
|
|
80
|
+
bal,
|
|
81
|
+
"cash_and_cash_equivalents_including_short_term_investments",
|
|
82
|
+
"cash_and_cash_equivalents",
|
|
83
|
+
"cash"
|
|
84
|
+
);
|
|
85
|
+
const currentLiab = _v(bal, "current_liabilities");
|
|
86
|
+
const totalEquity = _v(
|
|
87
|
+
bal,
|
|
88
|
+
"equity",
|
|
89
|
+
"stockholders_equity",
|
|
90
|
+
"equity_attributable_to_parent"
|
|
91
|
+
);
|
|
92
|
+
const longTermDebt = _v(bal, "long_term_debt");
|
|
93
|
+
const ar = _v(
|
|
94
|
+
bal,
|
|
95
|
+
"accounts_receivable",
|
|
96
|
+
"accounts_receivable_net_current",
|
|
97
|
+
"trade_and_other_receivables_current"
|
|
98
|
+
);
|
|
99
|
+
const ocf = _v(
|
|
100
|
+
cf,
|
|
101
|
+
"net_cash_flow_from_operating_activities",
|
|
102
|
+
"net_cash_provided_by_used_in_operating_activities"
|
|
103
|
+
);
|
|
104
|
+
const investingCf = _v(
|
|
105
|
+
cf,
|
|
106
|
+
"net_cash_flow_from_investing_activities",
|
|
107
|
+
"net_cash_provided_by_used_in_investing_activities"
|
|
108
|
+
);
|
|
109
|
+
const capexDirect = _v(
|
|
110
|
+
cf,
|
|
111
|
+
"payments_to_acquire_property_plant_and_equipment",
|
|
112
|
+
"capital_expenditures"
|
|
113
|
+
);
|
|
114
|
+
const capex = capexDirect !== 0 ? Math.abs(capexDirect) : Math.abs(investingCf);
|
|
115
|
+
const dividendsPaid = Math.abs(
|
|
116
|
+
_v(cf, "payments_of_dividends", "payments_of_dividends_common_stock", "dividends_paid")
|
|
117
|
+
);
|
|
118
|
+
let depreciation = _v(
|
|
119
|
+
inc,
|
|
120
|
+
"depreciation_and_amortization",
|
|
121
|
+
"depreciation_depletion_and_amortization"
|
|
122
|
+
);
|
|
123
|
+
if (depreciation === 0) {
|
|
124
|
+
depreciation = _v(
|
|
125
|
+
cf,
|
|
126
|
+
"depreciation_depletion_and_amortization",
|
|
127
|
+
"depreciation_and_amortization"
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
const sharesBS = _v(bal, "common_stock_shares_outstanding");
|
|
131
|
+
const sharesInc = _v(inc, "basic_average_shares", "diluted_average_shares");
|
|
132
|
+
const sharesOutstanding = sharesBS !== 0 ? sharesBS : sharesInc !== 0 ? sharesInc : void 0;
|
|
133
|
+
const year = String(item.fiscal_year ?? item.period_of_report_date?.slice(0, 4) ?? "");
|
|
134
|
+
return {
|
|
135
|
+
year,
|
|
136
|
+
revenue,
|
|
137
|
+
grossProfit,
|
|
138
|
+
ebit,
|
|
139
|
+
netIncome,
|
|
140
|
+
totalAssets,
|
|
141
|
+
totalEquity,
|
|
142
|
+
totalDebt: longTermDebt,
|
|
143
|
+
cash,
|
|
144
|
+
capex,
|
|
145
|
+
depreciation,
|
|
146
|
+
operatingCashFlow: ocf,
|
|
147
|
+
incomeTaxExpense: taxExpense,
|
|
148
|
+
ebt,
|
|
149
|
+
interestExpense: interestExp,
|
|
150
|
+
currentAssets,
|
|
151
|
+
currentLiabilities: currentLiab,
|
|
152
|
+
accountsReceivable: ar,
|
|
153
|
+
dividendsPaid,
|
|
154
|
+
...sharesOutstanding !== void 0 ? { sharesOutstanding } : {}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function _v(section, ...keys) {
|
|
158
|
+
for (const k of keys) {
|
|
159
|
+
const node = section[k];
|
|
160
|
+
if (node && node.value != null) {
|
|
161
|
+
const n = Number(node.value);
|
|
162
|
+
if (!isNaN(n)) return n;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
|
167
|
+
function _startDate(years) {
|
|
168
|
+
const d = /* @__PURE__ */ new Date();
|
|
169
|
+
d.setFullYear(d.getFullYear() - years);
|
|
170
|
+
return d.toISOString().slice(0, 10);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
exports.fetchPolygon = fetchPolygon;
|
|
174
|
+
exports.setPolygonApiKey = setPolygonApiKey;
|
|
175
|
+
//# sourceMappingURL=index.cjs.map
|
|
176
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/fetchers/polygon/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Polygon.io fetcher for fin-ratios (TypeScript).\n *\n * Fetches fundamental financial data from the Polygon.io REST API.\n * API key required — get one at https://polygon.io.\n * US companies only. Free tier: 5 req/min, ~2 years of history.\n *\n * @example\n * import { fetchPolygon, setPolygonApiKey } from 'fin-ratios/fetchers/polygon'\n * setPolygonApiKey('your-api-key')\n * const data = await fetchPolygon('AAPL', { numYears: 5 })\n * // data is oldest-first and compatible with all scoring utilities\n */\n\nconst POLYGON_BASE = 'https://api.polygon.io/vX/reference/financials'\n\n// ── Module-level key storage ───────────────────────────────────────────────────\n\nlet _moduleKey: string | undefined\n\n/**\n * Store a Polygon.io API key module-wide.\n * Subsequent calls to {@link fetchPolygon} will use this key unless overridden\n * via the {@link PolygonOptions.apiKey} option.\n */\nexport function setPolygonApiKey(key: string): void {\n _moduleKey = key\n}\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport interface PolygonOptions {\n /** Number of annual periods to retrieve (default: 5). */\n numYears?: number\n /**\n * Polygon.io API key. Falls back to the module-level key set via\n * {@link setPolygonApiKey} and then to the `POLYGON_API_KEY` environment\n * variable (Node.js only).\n */\n apiKey?: string\n /** ``'annual'`` (default) or ``'quarterly'``. */\n timeframe?: 'annual' | 'quarterly'\n}\n\n/**\n * Normalised annual financial record returned by {@link fetchPolygon}.\n * Field names are compatible with the `EdgarAnnualRecord` type used by the\n * EDGAR fetcher, so the data can be passed directly to all scoring utilities:\n * `moatScore`, `capitalAllocationScore`, `earningsQualityScore`,\n * `qualityScore`, `investmentScore`, etc.\n */\nexport interface PolygonAnnualRecord {\n /** Four-digit fiscal year string, e.g. ``'2023'``. */\n year: string\n revenue: number\n grossProfit: number\n /** Operating income (EBIT). */\n ebit: number\n netIncome: number\n totalAssets: number\n totalEquity: number\n /** Long-term debt (used as total debt proxy). */\n totalDebt: number\n cash: number\n /** Capital expenditures (absolute value). */\n capex: number\n depreciation: number\n operatingCashFlow: number\n incomeTaxExpense: number\n ebt: number\n interestExpense: number\n currentAssets: number\n currentLiabilities: number\n accountsReceivable: number\n /** Dividends paid (absolute value). Zero for non-payers. */\n dividendsPaid?: number\n /** Shares outstanding (or weighted-average basic shares). */\n sharesOutstanding?: number\n}\n\n// ── Internal Polygon response types ───────────────────────────────────────────\n\ninterface _PolygonValue {\n value?: number | null\n unit?: string\n label?: string\n}\n\ninterface _PolygonFinancials {\n income_statement?: Record<string, _PolygonValue>\n balance_sheet?: Record<string, _PolygonValue>\n cash_flow_statement?: Record<string, _PolygonValue>\n}\n\ninterface _PolygonResult {\n fiscal_year?: string | number\n period_of_report_date?: string\n financials?: _PolygonFinancials\n}\n\ninterface _PolygonResponse {\n results?: _PolygonResult[]\n status?: string\n request_id?: string\n next_url?: string\n}\n\n// ── Public API ─────────────────────────────────────────────────────────────────\n\n/**\n * Fetch annual financial data from Polygon.io.\n *\n * @param ticker - Stock ticker symbol (e.g. ``'AAPL'``).\n * @param options - Options including API key, number of years, and timeframe.\n * @returns Oldest-first list of annual records compatible with all scoring\n * utilities (moatScore, capitalAllocationScore, qualityScore, etc.).\n *\n * @throws {Error} If no API key is available.\n * @throws {Error} If the Polygon.io API request fails.\n *\n * @example\n * import { fetchPolygon } from 'fin-ratios/fetchers/polygon'\n * const data = await fetchPolygon('AAPL', { numYears: 5, apiKey: 'your-key' })\n */\nexport async function fetchPolygon(\n ticker: string,\n options: PolygonOptions = {}\n): Promise<PolygonAnnualRecord[]> {\n const { numYears = 5, timeframe = 'annual' } = options\n\n // Resolve API key: option > module-level > env var\n const apiKey =\n options.apiKey ??\n _moduleKey ??\n (typeof process !== 'undefined' ? process.env['POLYGON_API_KEY'] : undefined)\n\n if (!apiKey) {\n throw new Error(\n 'Polygon.io API key is required. Provide it via options.apiKey, ' +\n 'setPolygonApiKey(), or the POLYGON_API_KEY environment variable.'\n )\n }\n\n // Build start date: go back numYears years from today\n const startDate = _startDate(Math.max(numYears, 2))\n\n const params = new URLSearchParams({\n ticker: ticker.toUpperCase(),\n timeframe,\n limit: String(Math.min(numYears, 100)),\n sort: 'period_of_report_date',\n order: 'asc',\n 'filing_date.gte': startDate,\n apiKey,\n })\n\n const url = `${POLYGON_BASE}?${params.toString()}`\n\n let payload: _PolygonResponse\n try {\n const resp = await fetch(url)\n if (resp.status === 401) {\n throw new Error(\n 'Polygon API returned 401 Unauthorized — check your API key.'\n )\n }\n if (resp.status === 429) {\n throw new Error(\n 'Polygon API rate limit exceeded (free tier: 5 req/min). ' +\n 'Wait a moment and retry.'\n )\n }\n if (!resp.ok) {\n const body = await resp.text().catch(() => '')\n throw new Error(\n `Polygon API request failed for ${ticker}: HTTP ${resp.status} — ${body.slice(0, 200)}`\n )\n }\n payload = (await resp.json()) as _PolygonResponse\n } catch (err) {\n // Re-throw errors we constructed above; wrap unexpected fetch failures\n if (err instanceof Error && err.message.startsWith('Polygon API')) throw err\n throw new Error(\n `Polygon API request failed for ${ticker}: ${err instanceof Error ? err.message : String(err)}`\n )\n }\n\n const results = payload.results ?? []\n return results.map(item => _mapRecord(item))\n}\n\n// ── Mapping helpers ────────────────────────────────────────────────────────────\n\nfunction _mapRecord(item: _PolygonResult): PolygonAnnualRecord {\n const inc = item.financials?.income_statement ?? {}\n const bal = item.financials?.balance_sheet ?? {}\n const cf = item.financials?.cash_flow_statement ?? {}\n\n const revenue = _v(inc, 'revenues', 'net_revenues', 'total_revenues')\n const grossProfit = _v(inc, 'gross_profit')\n const ebit = _v(inc, 'operating_income',\n 'income_loss_from_continuing_operations_before_tax')\n const netIncome = _v(inc, 'net_income_loss', 'net_income_loss_attributable_to_parent')\n const taxExpense = _v(inc, 'income_tax_expense_benefit')\n const interestExp = Math.abs(_v(inc, 'interest_expense_operating', 'interest_expense'))\n\n // EBT: prefer direct field, fall back to net income + taxes\n const ebtDirect = _v(inc, 'income_loss_from_continuing_operations_before_tax',\n 'income_before_income_taxes')\n const ebt = ebtDirect !== 0 ? ebtDirect : netIncome + taxExpense\n\n const totalAssets = _v(bal, 'assets')\n const currentAssets = _v(bal, 'current_assets')\n const cash = _v(bal,\n 'cash_and_cash_equivalents_including_short_term_investments',\n 'cash_and_cash_equivalents', 'cash')\n const currentLiab = _v(bal, 'current_liabilities')\n const totalEquity = _v(bal, 'equity', 'stockholders_equity',\n 'equity_attributable_to_parent')\n const longTermDebt = _v(bal, 'long_term_debt')\n const ar = _v(bal, 'accounts_receivable',\n 'accounts_receivable_net_current',\n 'trade_and_other_receivables_current')\n\n const ocf = _v(cf, 'net_cash_flow_from_operating_activities',\n 'net_cash_provided_by_used_in_operating_activities')\n const investingCf = _v(cf, 'net_cash_flow_from_investing_activities',\n 'net_cash_provided_by_used_in_investing_activities')\n const capexDirect = _v(cf,\n 'payments_to_acquire_property_plant_and_equipment',\n 'capital_expenditures')\n const capex = capexDirect !== 0 ? Math.abs(capexDirect) : Math.abs(investingCf)\n\n const dividendsPaid = Math.abs(\n _v(cf, 'payments_of_dividends', 'payments_of_dividends_common_stock', 'dividends_paid')\n )\n\n let depreciation = _v(inc, 'depreciation_and_amortization',\n 'depreciation_depletion_and_amortization')\n if (depreciation === 0) {\n depreciation = _v(cf, 'depreciation_depletion_and_amortization',\n 'depreciation_and_amortization')\n }\n\n // Shares: prefer balance-sheet figure; fall back to income-statement weighted avg\n const sharesBS = _v(bal, 'common_stock_shares_outstanding')\n const sharesInc = _v(inc, 'basic_average_shares', 'diluted_average_shares')\n const sharesOutstanding = sharesBS !== 0 ? sharesBS : sharesInc !== 0 ? sharesInc : undefined\n\n const year = String(item.fiscal_year ?? item.period_of_report_date?.slice(0, 4) ?? '')\n\n return {\n year,\n revenue,\n grossProfit,\n ebit,\n netIncome,\n totalAssets,\n totalEquity,\n totalDebt: longTermDebt,\n cash,\n capex,\n depreciation,\n operatingCashFlow: ocf,\n incomeTaxExpense: taxExpense,\n ebt,\n interestExpense: interestExp,\n currentAssets,\n currentLiabilities: currentLiab,\n accountsReceivable: ar,\n dividendsPaid: dividendsPaid,\n ...(sharesOutstanding !== undefined ? { sharesOutstanding } : {}),\n }\n}\n\n/** Extract the first non-zero numeric value from a Polygon section by key priority. */\nfunction _v(\n section: Record<string, _PolygonValue>,\n ...keys: string[]\n): number {\n for (const k of keys) {\n const node = section[k]\n if (node && node.value != null) {\n const n = Number(node.value)\n if (!isNaN(n)) return n\n }\n }\n return 0\n}\n\n/** Return an ISO date string N years ago from today. */\nfunction _startDate(years: number): string {\n const d = new Date()\n d.setFullYear(d.getFullYear() - years)\n return d.toISOString().slice(0, 10)\n}\n"]}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polygon.io fetcher for fin-ratios (TypeScript).
|
|
3
|
+
*
|
|
4
|
+
* Fetches fundamental financial data from the Polygon.io REST API.
|
|
5
|
+
* API key required — get one at https://polygon.io.
|
|
6
|
+
* US companies only. Free tier: 5 req/min, ~2 years of history.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { fetchPolygon, setPolygonApiKey } from 'fin-ratios/fetchers/polygon'
|
|
10
|
+
* setPolygonApiKey('your-api-key')
|
|
11
|
+
* const data = await fetchPolygon('AAPL', { numYears: 5 })
|
|
12
|
+
* // data is oldest-first and compatible with all scoring utilities
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Store a Polygon.io API key module-wide.
|
|
16
|
+
* Subsequent calls to {@link fetchPolygon} will use this key unless overridden
|
|
17
|
+
* via the {@link PolygonOptions.apiKey} option.
|
|
18
|
+
*/
|
|
19
|
+
declare function setPolygonApiKey(key: string): void;
|
|
20
|
+
interface PolygonOptions {
|
|
21
|
+
/** Number of annual periods to retrieve (default: 5). */
|
|
22
|
+
numYears?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Polygon.io API key. Falls back to the module-level key set via
|
|
25
|
+
* {@link setPolygonApiKey} and then to the `POLYGON_API_KEY` environment
|
|
26
|
+
* variable (Node.js only).
|
|
27
|
+
*/
|
|
28
|
+
apiKey?: string;
|
|
29
|
+
/** ``'annual'`` (default) or ``'quarterly'``. */
|
|
30
|
+
timeframe?: 'annual' | 'quarterly';
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Normalised annual financial record returned by {@link fetchPolygon}.
|
|
34
|
+
* Field names are compatible with the `EdgarAnnualRecord` type used by the
|
|
35
|
+
* EDGAR fetcher, so the data can be passed directly to all scoring utilities:
|
|
36
|
+
* `moatScore`, `capitalAllocationScore`, `earningsQualityScore`,
|
|
37
|
+
* `qualityScore`, `investmentScore`, etc.
|
|
38
|
+
*/
|
|
39
|
+
interface PolygonAnnualRecord {
|
|
40
|
+
/** Four-digit fiscal year string, e.g. ``'2023'``. */
|
|
41
|
+
year: string;
|
|
42
|
+
revenue: number;
|
|
43
|
+
grossProfit: number;
|
|
44
|
+
/** Operating income (EBIT). */
|
|
45
|
+
ebit: number;
|
|
46
|
+
netIncome: number;
|
|
47
|
+
totalAssets: number;
|
|
48
|
+
totalEquity: number;
|
|
49
|
+
/** Long-term debt (used as total debt proxy). */
|
|
50
|
+
totalDebt: number;
|
|
51
|
+
cash: number;
|
|
52
|
+
/** Capital expenditures (absolute value). */
|
|
53
|
+
capex: number;
|
|
54
|
+
depreciation: number;
|
|
55
|
+
operatingCashFlow: number;
|
|
56
|
+
incomeTaxExpense: number;
|
|
57
|
+
ebt: number;
|
|
58
|
+
interestExpense: number;
|
|
59
|
+
currentAssets: number;
|
|
60
|
+
currentLiabilities: number;
|
|
61
|
+
accountsReceivable: number;
|
|
62
|
+
/** Dividends paid (absolute value). Zero for non-payers. */
|
|
63
|
+
dividendsPaid?: number;
|
|
64
|
+
/** Shares outstanding (or weighted-average basic shares). */
|
|
65
|
+
sharesOutstanding?: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Fetch annual financial data from Polygon.io.
|
|
69
|
+
*
|
|
70
|
+
* @param ticker - Stock ticker symbol (e.g. ``'AAPL'``).
|
|
71
|
+
* @param options - Options including API key, number of years, and timeframe.
|
|
72
|
+
* @returns Oldest-first list of annual records compatible with all scoring
|
|
73
|
+
* utilities (moatScore, capitalAllocationScore, qualityScore, etc.).
|
|
74
|
+
*
|
|
75
|
+
* @throws {Error} If no API key is available.
|
|
76
|
+
* @throws {Error} If the Polygon.io API request fails.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* import { fetchPolygon } from 'fin-ratios/fetchers/polygon'
|
|
80
|
+
* const data = await fetchPolygon('AAPL', { numYears: 5, apiKey: 'your-key' })
|
|
81
|
+
*/
|
|
82
|
+
declare function fetchPolygon(ticker: string, options?: PolygonOptions): Promise<PolygonAnnualRecord[]>;
|
|
83
|
+
|
|
84
|
+
export { type PolygonAnnualRecord, type PolygonOptions, fetchPolygon, setPolygonApiKey };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polygon.io fetcher for fin-ratios (TypeScript).
|
|
3
|
+
*
|
|
4
|
+
* Fetches fundamental financial data from the Polygon.io REST API.
|
|
5
|
+
* API key required — get one at https://polygon.io.
|
|
6
|
+
* US companies only. Free tier: 5 req/min, ~2 years of history.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { fetchPolygon, setPolygonApiKey } from 'fin-ratios/fetchers/polygon'
|
|
10
|
+
* setPolygonApiKey('your-api-key')
|
|
11
|
+
* const data = await fetchPolygon('AAPL', { numYears: 5 })
|
|
12
|
+
* // data is oldest-first and compatible with all scoring utilities
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Store a Polygon.io API key module-wide.
|
|
16
|
+
* Subsequent calls to {@link fetchPolygon} will use this key unless overridden
|
|
17
|
+
* via the {@link PolygonOptions.apiKey} option.
|
|
18
|
+
*/
|
|
19
|
+
declare function setPolygonApiKey(key: string): void;
|
|
20
|
+
interface PolygonOptions {
|
|
21
|
+
/** Number of annual periods to retrieve (default: 5). */
|
|
22
|
+
numYears?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Polygon.io API key. Falls back to the module-level key set via
|
|
25
|
+
* {@link setPolygonApiKey} and then to the `POLYGON_API_KEY` environment
|
|
26
|
+
* variable (Node.js only).
|
|
27
|
+
*/
|
|
28
|
+
apiKey?: string;
|
|
29
|
+
/** ``'annual'`` (default) or ``'quarterly'``. */
|
|
30
|
+
timeframe?: 'annual' | 'quarterly';
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Normalised annual financial record returned by {@link fetchPolygon}.
|
|
34
|
+
* Field names are compatible with the `EdgarAnnualRecord` type used by the
|
|
35
|
+
* EDGAR fetcher, so the data can be passed directly to all scoring utilities:
|
|
36
|
+
* `moatScore`, `capitalAllocationScore`, `earningsQualityScore`,
|
|
37
|
+
* `qualityScore`, `investmentScore`, etc.
|
|
38
|
+
*/
|
|
39
|
+
interface PolygonAnnualRecord {
|
|
40
|
+
/** Four-digit fiscal year string, e.g. ``'2023'``. */
|
|
41
|
+
year: string;
|
|
42
|
+
revenue: number;
|
|
43
|
+
grossProfit: number;
|
|
44
|
+
/** Operating income (EBIT). */
|
|
45
|
+
ebit: number;
|
|
46
|
+
netIncome: number;
|
|
47
|
+
totalAssets: number;
|
|
48
|
+
totalEquity: number;
|
|
49
|
+
/** Long-term debt (used as total debt proxy). */
|
|
50
|
+
totalDebt: number;
|
|
51
|
+
cash: number;
|
|
52
|
+
/** Capital expenditures (absolute value). */
|
|
53
|
+
capex: number;
|
|
54
|
+
depreciation: number;
|
|
55
|
+
operatingCashFlow: number;
|
|
56
|
+
incomeTaxExpense: number;
|
|
57
|
+
ebt: number;
|
|
58
|
+
interestExpense: number;
|
|
59
|
+
currentAssets: number;
|
|
60
|
+
currentLiabilities: number;
|
|
61
|
+
accountsReceivable: number;
|
|
62
|
+
/** Dividends paid (absolute value). Zero for non-payers. */
|
|
63
|
+
dividendsPaid?: number;
|
|
64
|
+
/** Shares outstanding (or weighted-average basic shares). */
|
|
65
|
+
sharesOutstanding?: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Fetch annual financial data from Polygon.io.
|
|
69
|
+
*
|
|
70
|
+
* @param ticker - Stock ticker symbol (e.g. ``'AAPL'``).
|
|
71
|
+
* @param options - Options including API key, number of years, and timeframe.
|
|
72
|
+
* @returns Oldest-first list of annual records compatible with all scoring
|
|
73
|
+
* utilities (moatScore, capitalAllocationScore, qualityScore, etc.).
|
|
74
|
+
*
|
|
75
|
+
* @throws {Error} If no API key is available.
|
|
76
|
+
* @throws {Error} If the Polygon.io API request fails.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* import { fetchPolygon } from 'fin-ratios/fetchers/polygon'
|
|
80
|
+
* const data = await fetchPolygon('AAPL', { numYears: 5, apiKey: 'your-key' })
|
|
81
|
+
*/
|
|
82
|
+
declare function fetchPolygon(ticker: string, options?: PolygonOptions): Promise<PolygonAnnualRecord[]>;
|
|
83
|
+
|
|
84
|
+
export { type PolygonAnnualRecord, type PolygonOptions, fetchPolygon, setPolygonApiKey };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// src/fetchers/polygon/index.ts
|
|
2
|
+
var POLYGON_BASE = "https://api.polygon.io/vX/reference/financials";
|
|
3
|
+
var _moduleKey;
|
|
4
|
+
function setPolygonApiKey(key) {
|
|
5
|
+
_moduleKey = key;
|
|
6
|
+
}
|
|
7
|
+
async function fetchPolygon(ticker, options = {}) {
|
|
8
|
+
const { numYears = 5, timeframe = "annual" } = options;
|
|
9
|
+
const apiKey = options.apiKey ?? _moduleKey ?? (typeof process !== "undefined" ? process.env["POLYGON_API_KEY"] : void 0);
|
|
10
|
+
if (!apiKey) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
"Polygon.io API key is required. Provide it via options.apiKey, setPolygonApiKey(), or the POLYGON_API_KEY environment variable."
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
const startDate = _startDate(Math.max(numYears, 2));
|
|
16
|
+
const params = new URLSearchParams({
|
|
17
|
+
ticker: ticker.toUpperCase(),
|
|
18
|
+
timeframe,
|
|
19
|
+
limit: String(Math.min(numYears, 100)),
|
|
20
|
+
sort: "period_of_report_date",
|
|
21
|
+
order: "asc",
|
|
22
|
+
"filing_date.gte": startDate,
|
|
23
|
+
apiKey
|
|
24
|
+
});
|
|
25
|
+
const url = `${POLYGON_BASE}?${params.toString()}`;
|
|
26
|
+
let payload;
|
|
27
|
+
try {
|
|
28
|
+
const resp = await fetch(url);
|
|
29
|
+
if (resp.status === 401) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
"Polygon API returned 401 Unauthorized \u2014 check your API key."
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
if (resp.status === 429) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
"Polygon API rate limit exceeded (free tier: 5 req/min). Wait a moment and retry."
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
if (!resp.ok) {
|
|
40
|
+
const body = await resp.text().catch(() => "");
|
|
41
|
+
throw new Error(
|
|
42
|
+
`Polygon API request failed for ${ticker}: HTTP ${resp.status} \u2014 ${body.slice(0, 200)}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
payload = await resp.json();
|
|
46
|
+
} catch (err) {
|
|
47
|
+
if (err instanceof Error && err.message.startsWith("Polygon API")) throw err;
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Polygon API request failed for ${ticker}: ${err instanceof Error ? err.message : String(err)}`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
const results = payload.results ?? [];
|
|
53
|
+
return results.map((item) => _mapRecord(item));
|
|
54
|
+
}
|
|
55
|
+
function _mapRecord(item) {
|
|
56
|
+
const inc = item.financials?.income_statement ?? {};
|
|
57
|
+
const bal = item.financials?.balance_sheet ?? {};
|
|
58
|
+
const cf = item.financials?.cash_flow_statement ?? {};
|
|
59
|
+
const revenue = _v(inc, "revenues", "net_revenues", "total_revenues");
|
|
60
|
+
const grossProfit = _v(inc, "gross_profit");
|
|
61
|
+
const ebit = _v(
|
|
62
|
+
inc,
|
|
63
|
+
"operating_income",
|
|
64
|
+
"income_loss_from_continuing_operations_before_tax"
|
|
65
|
+
);
|
|
66
|
+
const netIncome = _v(inc, "net_income_loss", "net_income_loss_attributable_to_parent");
|
|
67
|
+
const taxExpense = _v(inc, "income_tax_expense_benefit");
|
|
68
|
+
const interestExp = Math.abs(_v(inc, "interest_expense_operating", "interest_expense"));
|
|
69
|
+
const ebtDirect = _v(
|
|
70
|
+
inc,
|
|
71
|
+
"income_loss_from_continuing_operations_before_tax",
|
|
72
|
+
"income_before_income_taxes"
|
|
73
|
+
);
|
|
74
|
+
const ebt = ebtDirect !== 0 ? ebtDirect : netIncome + taxExpense;
|
|
75
|
+
const totalAssets = _v(bal, "assets");
|
|
76
|
+
const currentAssets = _v(bal, "current_assets");
|
|
77
|
+
const cash = _v(
|
|
78
|
+
bal,
|
|
79
|
+
"cash_and_cash_equivalents_including_short_term_investments",
|
|
80
|
+
"cash_and_cash_equivalents",
|
|
81
|
+
"cash"
|
|
82
|
+
);
|
|
83
|
+
const currentLiab = _v(bal, "current_liabilities");
|
|
84
|
+
const totalEquity = _v(
|
|
85
|
+
bal,
|
|
86
|
+
"equity",
|
|
87
|
+
"stockholders_equity",
|
|
88
|
+
"equity_attributable_to_parent"
|
|
89
|
+
);
|
|
90
|
+
const longTermDebt = _v(bal, "long_term_debt");
|
|
91
|
+
const ar = _v(
|
|
92
|
+
bal,
|
|
93
|
+
"accounts_receivable",
|
|
94
|
+
"accounts_receivable_net_current",
|
|
95
|
+
"trade_and_other_receivables_current"
|
|
96
|
+
);
|
|
97
|
+
const ocf = _v(
|
|
98
|
+
cf,
|
|
99
|
+
"net_cash_flow_from_operating_activities",
|
|
100
|
+
"net_cash_provided_by_used_in_operating_activities"
|
|
101
|
+
);
|
|
102
|
+
const investingCf = _v(
|
|
103
|
+
cf,
|
|
104
|
+
"net_cash_flow_from_investing_activities",
|
|
105
|
+
"net_cash_provided_by_used_in_investing_activities"
|
|
106
|
+
);
|
|
107
|
+
const capexDirect = _v(
|
|
108
|
+
cf,
|
|
109
|
+
"payments_to_acquire_property_plant_and_equipment",
|
|
110
|
+
"capital_expenditures"
|
|
111
|
+
);
|
|
112
|
+
const capex = capexDirect !== 0 ? Math.abs(capexDirect) : Math.abs(investingCf);
|
|
113
|
+
const dividendsPaid = Math.abs(
|
|
114
|
+
_v(cf, "payments_of_dividends", "payments_of_dividends_common_stock", "dividends_paid")
|
|
115
|
+
);
|
|
116
|
+
let depreciation = _v(
|
|
117
|
+
inc,
|
|
118
|
+
"depreciation_and_amortization",
|
|
119
|
+
"depreciation_depletion_and_amortization"
|
|
120
|
+
);
|
|
121
|
+
if (depreciation === 0) {
|
|
122
|
+
depreciation = _v(
|
|
123
|
+
cf,
|
|
124
|
+
"depreciation_depletion_and_amortization",
|
|
125
|
+
"depreciation_and_amortization"
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
const sharesBS = _v(bal, "common_stock_shares_outstanding");
|
|
129
|
+
const sharesInc = _v(inc, "basic_average_shares", "diluted_average_shares");
|
|
130
|
+
const sharesOutstanding = sharesBS !== 0 ? sharesBS : sharesInc !== 0 ? sharesInc : void 0;
|
|
131
|
+
const year = String(item.fiscal_year ?? item.period_of_report_date?.slice(0, 4) ?? "");
|
|
132
|
+
return {
|
|
133
|
+
year,
|
|
134
|
+
revenue,
|
|
135
|
+
grossProfit,
|
|
136
|
+
ebit,
|
|
137
|
+
netIncome,
|
|
138
|
+
totalAssets,
|
|
139
|
+
totalEquity,
|
|
140
|
+
totalDebt: longTermDebt,
|
|
141
|
+
cash,
|
|
142
|
+
capex,
|
|
143
|
+
depreciation,
|
|
144
|
+
operatingCashFlow: ocf,
|
|
145
|
+
incomeTaxExpense: taxExpense,
|
|
146
|
+
ebt,
|
|
147
|
+
interestExpense: interestExp,
|
|
148
|
+
currentAssets,
|
|
149
|
+
currentLiabilities: currentLiab,
|
|
150
|
+
accountsReceivable: ar,
|
|
151
|
+
dividendsPaid,
|
|
152
|
+
...sharesOutstanding !== void 0 ? { sharesOutstanding } : {}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function _v(section, ...keys) {
|
|
156
|
+
for (const k of keys) {
|
|
157
|
+
const node = section[k];
|
|
158
|
+
if (node && node.value != null) {
|
|
159
|
+
const n = Number(node.value);
|
|
160
|
+
if (!isNaN(n)) return n;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
|
165
|
+
function _startDate(years) {
|
|
166
|
+
const d = /* @__PURE__ */ new Date();
|
|
167
|
+
d.setFullYear(d.getFullYear() - years);
|
|
168
|
+
return d.toISOString().slice(0, 10);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export { fetchPolygon, setPolygonApiKey };
|
|
172
|
+
//# sourceMappingURL=index.js.map
|
|
173
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/fetchers/polygon/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Polygon.io fetcher for fin-ratios (TypeScript).\n *\n * Fetches fundamental financial data from the Polygon.io REST API.\n * API key required — get one at https://polygon.io.\n * US companies only. Free tier: 5 req/min, ~2 years of history.\n *\n * @example\n * import { fetchPolygon, setPolygonApiKey } from 'fin-ratios/fetchers/polygon'\n * setPolygonApiKey('your-api-key')\n * const data = await fetchPolygon('AAPL', { numYears: 5 })\n * // data is oldest-first and compatible with all scoring utilities\n */\n\nconst POLYGON_BASE = 'https://api.polygon.io/vX/reference/financials'\n\n// ── Module-level key storage ───────────────────────────────────────────────────\n\nlet _moduleKey: string | undefined\n\n/**\n * Store a Polygon.io API key module-wide.\n * Subsequent calls to {@link fetchPolygon} will use this key unless overridden\n * via the {@link PolygonOptions.apiKey} option.\n */\nexport function setPolygonApiKey(key: string): void {\n _moduleKey = key\n}\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport interface PolygonOptions {\n /** Number of annual periods to retrieve (default: 5). */\n numYears?: number\n /**\n * Polygon.io API key. Falls back to the module-level key set via\n * {@link setPolygonApiKey} and then to the `POLYGON_API_KEY` environment\n * variable (Node.js only).\n */\n apiKey?: string\n /** ``'annual'`` (default) or ``'quarterly'``. */\n timeframe?: 'annual' | 'quarterly'\n}\n\n/**\n * Normalised annual financial record returned by {@link fetchPolygon}.\n * Field names are compatible with the `EdgarAnnualRecord` type used by the\n * EDGAR fetcher, so the data can be passed directly to all scoring utilities:\n * `moatScore`, `capitalAllocationScore`, `earningsQualityScore`,\n * `qualityScore`, `investmentScore`, etc.\n */\nexport interface PolygonAnnualRecord {\n /** Four-digit fiscal year string, e.g. ``'2023'``. */\n year: string\n revenue: number\n grossProfit: number\n /** Operating income (EBIT). */\n ebit: number\n netIncome: number\n totalAssets: number\n totalEquity: number\n /** Long-term debt (used as total debt proxy). */\n totalDebt: number\n cash: number\n /** Capital expenditures (absolute value). */\n capex: number\n depreciation: number\n operatingCashFlow: number\n incomeTaxExpense: number\n ebt: number\n interestExpense: number\n currentAssets: number\n currentLiabilities: number\n accountsReceivable: number\n /** Dividends paid (absolute value). Zero for non-payers. */\n dividendsPaid?: number\n /** Shares outstanding (or weighted-average basic shares). */\n sharesOutstanding?: number\n}\n\n// ── Internal Polygon response types ───────────────────────────────────────────\n\ninterface _PolygonValue {\n value?: number | null\n unit?: string\n label?: string\n}\n\ninterface _PolygonFinancials {\n income_statement?: Record<string, _PolygonValue>\n balance_sheet?: Record<string, _PolygonValue>\n cash_flow_statement?: Record<string, _PolygonValue>\n}\n\ninterface _PolygonResult {\n fiscal_year?: string | number\n period_of_report_date?: string\n financials?: _PolygonFinancials\n}\n\ninterface _PolygonResponse {\n results?: _PolygonResult[]\n status?: string\n request_id?: string\n next_url?: string\n}\n\n// ── Public API ─────────────────────────────────────────────────────────────────\n\n/**\n * Fetch annual financial data from Polygon.io.\n *\n * @param ticker - Stock ticker symbol (e.g. ``'AAPL'``).\n * @param options - Options including API key, number of years, and timeframe.\n * @returns Oldest-first list of annual records compatible with all scoring\n * utilities (moatScore, capitalAllocationScore, qualityScore, etc.).\n *\n * @throws {Error} If no API key is available.\n * @throws {Error} If the Polygon.io API request fails.\n *\n * @example\n * import { fetchPolygon } from 'fin-ratios/fetchers/polygon'\n * const data = await fetchPolygon('AAPL', { numYears: 5, apiKey: 'your-key' })\n */\nexport async function fetchPolygon(\n ticker: string,\n options: PolygonOptions = {}\n): Promise<PolygonAnnualRecord[]> {\n const { numYears = 5, timeframe = 'annual' } = options\n\n // Resolve API key: option > module-level > env var\n const apiKey =\n options.apiKey ??\n _moduleKey ??\n (typeof process !== 'undefined' ? process.env['POLYGON_API_KEY'] : undefined)\n\n if (!apiKey) {\n throw new Error(\n 'Polygon.io API key is required. Provide it via options.apiKey, ' +\n 'setPolygonApiKey(), or the POLYGON_API_KEY environment variable.'\n )\n }\n\n // Build start date: go back numYears years from today\n const startDate = _startDate(Math.max(numYears, 2))\n\n const params = new URLSearchParams({\n ticker: ticker.toUpperCase(),\n timeframe,\n limit: String(Math.min(numYears, 100)),\n sort: 'period_of_report_date',\n order: 'asc',\n 'filing_date.gte': startDate,\n apiKey,\n })\n\n const url = `${POLYGON_BASE}?${params.toString()}`\n\n let payload: _PolygonResponse\n try {\n const resp = await fetch(url)\n if (resp.status === 401) {\n throw new Error(\n 'Polygon API returned 401 Unauthorized — check your API key.'\n )\n }\n if (resp.status === 429) {\n throw new Error(\n 'Polygon API rate limit exceeded (free tier: 5 req/min). ' +\n 'Wait a moment and retry.'\n )\n }\n if (!resp.ok) {\n const body = await resp.text().catch(() => '')\n throw new Error(\n `Polygon API request failed for ${ticker}: HTTP ${resp.status} — ${body.slice(0, 200)}`\n )\n }\n payload = (await resp.json()) as _PolygonResponse\n } catch (err) {\n // Re-throw errors we constructed above; wrap unexpected fetch failures\n if (err instanceof Error && err.message.startsWith('Polygon API')) throw err\n throw new Error(\n `Polygon API request failed for ${ticker}: ${err instanceof Error ? err.message : String(err)}`\n )\n }\n\n const results = payload.results ?? []\n return results.map(item => _mapRecord(item))\n}\n\n// ── Mapping helpers ────────────────────────────────────────────────────────────\n\nfunction _mapRecord(item: _PolygonResult): PolygonAnnualRecord {\n const inc = item.financials?.income_statement ?? {}\n const bal = item.financials?.balance_sheet ?? {}\n const cf = item.financials?.cash_flow_statement ?? {}\n\n const revenue = _v(inc, 'revenues', 'net_revenues', 'total_revenues')\n const grossProfit = _v(inc, 'gross_profit')\n const ebit = _v(inc, 'operating_income',\n 'income_loss_from_continuing_operations_before_tax')\n const netIncome = _v(inc, 'net_income_loss', 'net_income_loss_attributable_to_parent')\n const taxExpense = _v(inc, 'income_tax_expense_benefit')\n const interestExp = Math.abs(_v(inc, 'interest_expense_operating', 'interest_expense'))\n\n // EBT: prefer direct field, fall back to net income + taxes\n const ebtDirect = _v(inc, 'income_loss_from_continuing_operations_before_tax',\n 'income_before_income_taxes')\n const ebt = ebtDirect !== 0 ? ebtDirect : netIncome + taxExpense\n\n const totalAssets = _v(bal, 'assets')\n const currentAssets = _v(bal, 'current_assets')\n const cash = _v(bal,\n 'cash_and_cash_equivalents_including_short_term_investments',\n 'cash_and_cash_equivalents', 'cash')\n const currentLiab = _v(bal, 'current_liabilities')\n const totalEquity = _v(bal, 'equity', 'stockholders_equity',\n 'equity_attributable_to_parent')\n const longTermDebt = _v(bal, 'long_term_debt')\n const ar = _v(bal, 'accounts_receivable',\n 'accounts_receivable_net_current',\n 'trade_and_other_receivables_current')\n\n const ocf = _v(cf, 'net_cash_flow_from_operating_activities',\n 'net_cash_provided_by_used_in_operating_activities')\n const investingCf = _v(cf, 'net_cash_flow_from_investing_activities',\n 'net_cash_provided_by_used_in_investing_activities')\n const capexDirect = _v(cf,\n 'payments_to_acquire_property_plant_and_equipment',\n 'capital_expenditures')\n const capex = capexDirect !== 0 ? Math.abs(capexDirect) : Math.abs(investingCf)\n\n const dividendsPaid = Math.abs(\n _v(cf, 'payments_of_dividends', 'payments_of_dividends_common_stock', 'dividends_paid')\n )\n\n let depreciation = _v(inc, 'depreciation_and_amortization',\n 'depreciation_depletion_and_amortization')\n if (depreciation === 0) {\n depreciation = _v(cf, 'depreciation_depletion_and_amortization',\n 'depreciation_and_amortization')\n }\n\n // Shares: prefer balance-sheet figure; fall back to income-statement weighted avg\n const sharesBS = _v(bal, 'common_stock_shares_outstanding')\n const sharesInc = _v(inc, 'basic_average_shares', 'diluted_average_shares')\n const sharesOutstanding = sharesBS !== 0 ? sharesBS : sharesInc !== 0 ? sharesInc : undefined\n\n const year = String(item.fiscal_year ?? item.period_of_report_date?.slice(0, 4) ?? '')\n\n return {\n year,\n revenue,\n grossProfit,\n ebit,\n netIncome,\n totalAssets,\n totalEquity,\n totalDebt: longTermDebt,\n cash,\n capex,\n depreciation,\n operatingCashFlow: ocf,\n incomeTaxExpense: taxExpense,\n ebt,\n interestExpense: interestExp,\n currentAssets,\n currentLiabilities: currentLiab,\n accountsReceivable: ar,\n dividendsPaid: dividendsPaid,\n ...(sharesOutstanding !== undefined ? { sharesOutstanding } : {}),\n }\n}\n\n/** Extract the first non-zero numeric value from a Polygon section by key priority. */\nfunction _v(\n section: Record<string, _PolygonValue>,\n ...keys: string[]\n): number {\n for (const k of keys) {\n const node = section[k]\n if (node && node.value != null) {\n const n = Number(node.value)\n if (!isNaN(n)) return n\n }\n }\n return 0\n}\n\n/** Return an ISO date string N years ago from today. */\nfunction _startDate(years: number): string {\n const d = new Date()\n d.setFullYear(d.getFullYear() - years)\n return d.toISOString().slice(0, 10)\n}\n"]}
|