UNPKG

sec-edgar-api

Version:

Fetch and parse SEC earnings reports and other filings. Useful for financial analysis.

254 lines (253 loc) 13.6 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; Object.defineProperty(exports, "__esModule", { value: true }); var constants_1 = require("../../util/constants"); var FactPeriodResolver_1 = require("./FactPeriodResolver"); /** * Splits can be filed multiple times throughout different reports. There is no clear * indication on when the split is executed or what facts the split has been applied to. This * class tries to determine which splits have been applied and which need to be adjusted for * each fact. */ var FactSplitAdjuster = /** @class */ (function () { function FactSplitAdjuster() { this.keySplit = constants_1.KEY_SPLIT; this.preferFirstValue = true; } FactSplitAdjuster.prototype.getGroupValue = function (factGroup, isTrailing) { if (isTrailing) { return Number(this.preferFirstValue ? factGroup.valueTrailingFirst : factGroup.valueTrailingLast); } else { return Number(this.preferFirstValue ? factGroup.valuePeriodFirst : factGroup.valuePeriodLast); } }; FactSplitAdjuster.prototype.getSplits = function (params) { var _a, _b; var splitFacts = __spreadArray([], params.splitFacts, true).sort(function (a, b) { return (a.end < b.end ? -1 : 1); }); var YEAR_MS = 31536000000; var splits = []; var currentSplit = null; for (var i = 0; i < splitFacts.length; i++) { var prevFact = splitFacts[i - 1]; var fact = splitFacts[i]; var factValue = Number((_a = fact.value) !== null && _a !== void 0 ? _a : fact.val); var factValuePrev = Number((_b = prevFact === null || prevFact === void 0 ? void 0 : prevFact.value) !== null && _b !== void 0 ? _b : prevFact === null || prevFact === void 0 ? void 0 : prevFact.val); var isSameSplitLaterFiling = factValue === factValuePrev && new Date(fact.end).getTime() - new Date(prevFact.end).getTime() < YEAR_MS; if (!isSameSplitLaterFiling && currentSplit) { splits.push(currentSplit); currentSplit = null; } if (!currentSplit) { currentSplit = { endFirst: fact.end, endLast: fact.end, filedFirst: fact.filed, filedLast: fact.filed, splitRatio: factValue, }; } else { currentSplit.endFirst = fact.end < currentSplit.endFirst ? fact.end : currentSplit.endFirst; currentSplit.endLast = fact.end > currentSplit.endLast ? fact.end : currentSplit.endLast; currentSplit.filedFirst = fact.filed < currentSplit.filedFirst ? fact.filed : currentSplit.filedFirst; currentSplit.filedLast = fact.filed > currentSplit.filedLast ? fact.filed : currentSplit.filedLast; } } if (currentSplit) { splits.push(currentSplit); } return splits; }; FactSplitAdjuster.prototype.filterSplitFacts = function (params) { var _this = this; var facts = params.facts; return facts.filter(function (f) { return f.name.endsWith(_this.keySplit); }); }; FactSplitAdjuster.prototype.extractSplitsFromCompanyFacts = function (params) { var _a, _b, _c; var companyFactList = params.companyFactList; var factsByName = (_a = companyFactList.facts['us-gaap']) !== null && _a !== void 0 ? _a : {}; return (_c = (_b = factsByName[this.keySplit]) === null || _b === void 0 ? void 0 : _b.units.pure) !== null && _c !== void 0 ? _c : []; }; FactSplitAdjuster.prototype.getSplitsFromCompanyFacts = function (params) { var _a, _b, _c; var companyFactList = params.companyFactList; var factsByName = (_a = companyFactList.facts['us-gaap']) !== null && _a !== void 0 ? _a : {}; var splitFacts = __spreadArray([], ((_c = (_b = factsByName[this.keySplit]) === null || _b === void 0 ? void 0 : _b.units.pure) !== null && _c !== void 0 ? _c : []), true).sort(function (a, b) { return (a.end < b.end ? -1 : 1); }); var YEAR_MS = 31536000000; var splits = []; var currentSplit = null; for (var i = 0; i < splitFacts.length; i++) { var prevFact = splitFacts[i - 1]; var fact = splitFacts[i]; // Assume the split is executed within the first year of the first filing... // sometimes a company will file the split fact mentioning that they plan on executing it later in the fiscal year // (ex: when Google did their 20:1 split in July of 2020) var isSameSplitLaterFiling = fact.val === (prevFact === null || prevFact === void 0 ? void 0 : prevFact.val) && new Date(fact.end).getTime() - new Date(prevFact.end).getTime() < YEAR_MS; if (!isSameSplitLaterFiling && currentSplit) { splits.push(currentSplit); currentSplit = null; } if (!currentSplit) { currentSplit = { endFirst: fact.end, endLast: fact.end, filedFirst: fact.filed, filedLast: fact.filed, splitRatio: Number(fact.val), }; } else { currentSplit.endFirst = fact.end < currentSplit.endFirst ? fact.end : currentSplit.endFirst; currentSplit.endLast = fact.end > currentSplit.endLast ? fact.end : currentSplit.endLast; currentSplit.filedFirst = fact.filed < currentSplit.filedFirst ? fact.filed : currentSplit.filedFirst; currentSplit.filedLast = fact.filed > currentSplit.filedLast ? fact.filed : currentSplit.filedLast; } } if (currentSplit) { splits.push(currentSplit); } return splits; }; /** * Returns true if the fact value was adjusted, false if it was not, * and null if the comparison does not match the split */ FactSplitAdjuster.prototype.isAdjustedFromComparedFact = function (params) { var _a, _b; var factCompare = params.factCompare, factValue = params.factValue, isShareRatio = params.isShareRatio, splitVal = params.splitVal; var minValue = Math.min(factValue, Number((_a = factCompare === null || factCompare === void 0 ? void 0 : factCompare.value) !== null && _a !== void 0 ? _a : factValue)); var maxValue = Math.max(factValue, Number((_b = factCompare === null || factCompare === void 0 ? void 0 : factCompare.value) !== null && _b !== void 0 ? _b : factValue)); var possiblePreSplitValue = isShareRatio ? maxValue : minValue; var possiblePostSplitValue = isShareRatio ? minValue : maxValue; var expectedPostSplitValue = isShareRatio ? possiblePreSplitValue / splitVal : possiblePreSplitValue * splitVal; var nearnessThreshold = 0.01; var isSplitAdjustment = Math.abs(expectedPostSplitValue - possiblePostSplitValue) < nearnessThreshold; return isSplitAdjustment ? possiblePostSplitValue === factValue : null; }; /** * Splits can be filed multiple times throughout different reports. */ FactSplitAdjuster.prototype.didApplySplit = function (params) { var _a; var isShareRatio = params.isShareRatio, factGroup = params.factGroup, split = params.split, isTrailing = params.isTrailing, _b = params.useOppositePeriodFallback, useOppositePeriodFallback = _b === void 0 ? true : _b; var splitVal = split.splitRatio; // string values are not adjusted if (typeof ((_a = factGroup.valuePeriodFirst) !== null && _a !== void 0 ? _a : factGroup.valueTrailingFirst) === 'string') { return true; } // can't apply split values of 0 or null if (!splitVal) { return true; } // these two criteria will take care of the majority of cases where the fact was not filed in // the window of the first and last filing of the split if (factGroup.filedFirst > split.filedLast) { return true; } if (factGroup.filedLast < split.filedFirst) { return false; } // fact that is being used as the group value var resolvedFact = factGroup.facts.find(function (f) { return isTrailing ? f.value === factGroup.valueTrailingFirst : f.value === factGroup.valuePeriodFirst; }); // if resolved fact not found, try checking trailing or period (whichever is not the current one) if (!resolvedFact && useOppositePeriodFallback) { return this.didApplySplit(__assign(__assign({}, params), { isTrailing: !isTrailing, useOppositePeriodFallback: false })); } var refiledFacts = factGroup.facts.filter(function (f) { var period = FactPeriodResolver_1.default.getPeriod(f); var isSamePeriod = isTrailing ? period === 0 || period > 3 : period <= 3; return f !== resolvedFact && isSamePeriod; }); // check if one of the filed facts is the split adjustment for (var _i = 0, refiledFacts_1 = refiledFacts; _i < refiledFacts_1.length; _i++) { var fact = refiledFacts_1[_i]; var isAdjusted = this.isAdjustedFromComparedFact({ factCompare: fact, factValue: Number(resolvedFact === null || resolvedFact === void 0 ? void 0 : resolvedFact.value), isShareRatio: isShareRatio, splitVal: splitVal, }); if (isAdjusted !== null) { return isAdjusted; } } if ((resolvedFact === null || resolvedFact === void 0 ? void 0 : resolvedFact.filed) && resolvedFact.filed > split.filedLast) { return true; } if ((resolvedFact === null || resolvedFact === void 0 ? void 0 : resolvedFact.filed) && resolvedFact.filed < split.filedFirst) { return false; } // // if the filed date of the fact overlaps with the filed date of the split, try comparing the end dates if (factGroup.endLast < split.endFirst && factGroup.values.length === 1) { return false; } // if we still don't know, see if the split value puts us closer to the last known value or further if (typeof factGroup.valuePeriodLast === 'number') { var val = this.getGroupValue(factGroup, isTrailing); var valueWithSplit = isShareRatio ? val / splitVal : val * splitVal; return Math.abs(factGroup.valuePeriodLast - val) < Math.abs(factGroup.valuePeriodLast - valueWithSplit); } if (typeof factGroup.valueTrailingLast === 'number') { var val = this.getGroupValue(factGroup, isTrailing); var valueWithSplit = isShareRatio ? val / splitVal : val * splitVal; return Math.abs(factGroup.valueTrailingLast - val) < Math.abs(factGroup.valueTrailingLast - valueWithSplit); } return true; }; FactSplitAdjuster.prototype.adjustForSplits = function (params) { var factGroups = params.factGroups, splits = params.splits; for (var _i = 0, factGroups_1 = factGroups; _i < factGroups_1.length; _i++) { var factGroup = factGroups_1[_i]; var unitLower = factGroup.unit.toLowerCase(); if (!unitLower.includes('share')) continue; var isShareRatio = unitLower !== 'shares'; for (var _a = 0, splits_1 = splits; _a < splits_1.length; _a++) { var split = splits_1[_a]; var factValuePeriod = this.getGroupValue(factGroup, false); var factValueTrailing = this.getGroupValue(factGroup, true); var splitValue = split.splitRatio; if (!splitValue) continue; // ratios (like EPS) get divided by splits, share counts get multiplied (like shares outstanding). if (!this.didApplySplit({ factGroup: factGroup, split: split, isShareRatio: isShareRatio, isTrailing: false })) { factGroup.valueSplitAdjustedPeriod = isShareRatio ? factValuePeriod / splitValue : factValuePeriod * splitValue; } if (!this.didApplySplit({ factGroup: factGroup, split: split, isShareRatio: isShareRatio, isTrailing: true })) { factGroup.valueSplitAdjustedTrailing = isShareRatio ? factValueTrailing / splitValue : factValueTrailing * splitValue; } } } }; return FactSplitAdjuster; }()); exports.default = FactSplitAdjuster;