sec-edgar-api
Version:
Fetch and parse SEC earnings reports and other filings. Useful for financial analysis.
254 lines (253 loc) • 13.6 kB
JavaScript
;
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;