UNPKG

@stoqey/ibkr

Version:

NodeJS Interactive Brokers wrapper & utilities using @stoqey/ib

202 lines 9.22 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 __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.defaultAggregators = void 0; exports.parseInterval = parseInterval; exports.getIntervalStart = getIntervalStart; exports.aggregateByInterval = aggregateByInterval; exports.createAggregator = createAggregator; exports.aggregateWithStats = aggregateWithStats; var lodash_1 = __importDefault(require("lodash")); /** * Parse interval string like '1s', '5m', '1h', etc. */ function parseInterval(interval) { if (interval === void 0) { interval = '1m'; } var match = interval.match(/^(\d+)([smhdwMy])$/); if (!match) { throw new Error("Invalid interval format: ".concat(interval, ". Use format like '1s', '5m', '1h', etc.")); } return { value: parseInt(match[1]), unit: match[2] }; } /** * Get the start of an interval for a given date */ function getIntervalStart(date, config) { var d = new Date(date); switch (config.unit) { case 's': d.setSeconds(Math.floor(d.getSeconds() / config.value) * config.value); d.setMilliseconds(0); break; case 'm': d.setMinutes(Math.floor(d.getMinutes() / config.value) * config.value); d.setSeconds(0); d.setMilliseconds(0); break; case 'h': d.setHours(Math.floor(d.getHours() / config.value) * config.value); d.setMinutes(0); d.setSeconds(0); d.setMilliseconds(0); break; case 'd': d.setHours(0, 0, 0, 0); if (config.value > 1) { var daysSinceEpoch = Math.floor(d.getTime() / (86400000)); var intervalStart = Math.floor(daysSinceEpoch / config.value) * config.value; d.setTime(intervalStart * 86400000); } break; case 'w': var dayOfWeek = d.getDay(); var diff = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // Monday = 0 d.setDate(d.getDate() - diff); d.setHours(0, 0, 0, 0); if (config.value > 1) { var weeksSinceEpoch = Math.floor(d.getTime() / (604800000)); var intervalStart = Math.floor(weeksSinceEpoch / config.value) * config.value; d.setTime(intervalStart * 604800000); } break; case 'M': d.setDate(1); d.setHours(0, 0, 0, 0); if (config.value > 1) { var monthsSinceEpoch = d.getFullYear() * 12 + d.getMonth(); var intervalStart = Math.floor(monthsSinceEpoch / config.value) * config.value; d.setFullYear(Math.floor(intervalStart / 12)); d.setMonth(intervalStart % 12); } break; case 'y': d.setMonth(0, 1); d.setHours(0, 0, 0, 0); if (config.value > 1) { d.setFullYear(Math.floor(d.getFullYear() / config.value) * config.value); } break; } return d; } /** * Default aggregators using lodash */ exports.defaultAggregators = { sum: function (values) { return lodash_1.default.sum(values); }, mean: function (values) { return lodash_1.default.mean(values); }, avg: function (values) { return lodash_1.default.mean(values); }, // Alias for mean average: function (values) { return lodash_1.default.mean(values); }, // Another alias median: function (values) { var sorted = lodash_1.default.sortBy(values); var mid = Math.floor(sorted.length / 2); return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2; }, min: function (values) { return lodash_1.default.min(values); }, max: function (values) { return lodash_1.default.max(values); }, first: function (values) { return lodash_1.default.first(values); }, last: function (values) { return lodash_1.default.last(values); }, count: function (values) { return values.length; }, stdDev: function (values) { var mean = lodash_1.default.mean(values); var variance = lodash_1.default.mean(values.map(function (v) { return Math.pow(v - mean, 2); })); return Math.sqrt(variance); } }; /** * Aggregate market data by time intervals * @param data Array of market data points * @param interval Interval string (e.g., '1s', '5m', '1h', '1d', '1w', '1M', '1y') * @param customAggregators Optional custom aggregation functions for additional fields */ function aggregateByInterval(data, interval, customAggregators) { if (lodash_1.default.isEmpty(data)) { return []; } var config = parseInterval(interval); // Group data by interval using lodash var grouped = lodash_1.default.groupBy(data, function (item) { return getIntervalStart(item.date, config).toISOString(); }); // Aggregate each group var results = lodash_1.default.map(grouped, function (items, intervalKey) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; // Sort items by date var sortedItems = lodash_1.default.sortBy(items, function (item) { return item.date.getTime(); }); // Determine if we're working with tick data (price) or OHLC data var isTickData = sortedItems[0].price !== undefined; var ohlc; if (isTickData) { // For tick data, we need to construct OHLC from prices var prices = lodash_1.default.compact(sortedItems.map(function (item) { return item.price; })); ohlc = { open: (_a = lodash_1.default.first(prices)) !== null && _a !== void 0 ? _a : 0, high: (_b = lodash_1.default.max(prices)) !== null && _b !== void 0 ? _b : 0, low: (_c = lodash_1.default.min(prices)) !== null && _c !== void 0 ? _c : 0, close: (_d = lodash_1.default.last(prices)) !== null && _d !== void 0 ? _d : 0, }; } else { // For pre-aggregated OHLC data, we need to aggregate properly ohlc = { open: (_f = (_e = lodash_1.default.first(sortedItems)) === null || _e === void 0 ? void 0 : _e.open) !== null && _f !== void 0 ? _f : 0, high: (_g = lodash_1.default.max(lodash_1.default.compact(sortedItems.map(function (item) { return item.high; })))) !== null && _g !== void 0 ? _g : 0, low: (_h = lodash_1.default.min(lodash_1.default.compact(sortedItems.map(function (item) { return item.low; })))) !== null && _h !== void 0 ? _h : 0, close: (_k = (_j = lodash_1.default.last(sortedItems)) === null || _j === void 0 ? void 0 : _j.close) !== null && _k !== void 0 ? _k : 0, }; } var aggregated = __assign(__assign({ interval: new Date(intervalKey), date: new Date(intervalKey) }, ohlc), { volume: lodash_1.default.sumBy(sortedItems, 'volume') || 0, count: sortedItems.length }); // Get all unique fields across all items var allFields = lodash_1.default.uniq(lodash_1.default.flatMap(sortedItems, Object.keys)); // Apply custom aggregators for additional fields var fieldsToAggregate = lodash_1.default.without(allFields, 'date', 'open', 'high', 'low', 'close', 'volume', 'price'); lodash_1.default.forEach(fieldsToAggregate, function (field) { var values = lodash_1.default.compact(sortedItems.map(function (item) { return item[field]; })); if (!lodash_1.default.isEmpty(values)) { if (customAggregators === null || customAggregators === void 0 ? void 0 : customAggregators[field]) { aggregated[field] = customAggregators[field](values); } } }); return aggregated; }); // Sort results by interval return lodash_1.default.sortBy(results, 'interval'); } /** * Helper function to create common aggregation patterns */ function createAggregator(data, interval, fieldAggregators) { var aggregators = lodash_1.default.mapValues(fieldAggregators, function (aggregator) { return lodash_1.default.isString(aggregator) ? exports.defaultAggregators[aggregator] : aggregator; }); return aggregateByInterval(data, interval, aggregators); } // Utility function for common statistical aggregations function aggregateWithStats(data, interval) { return createAggregator(data, interval, { priceStdDev: function (values) { return exports.defaultAggregators.stdDev(values); }, priceMean: 'mean', priceMedian: 'median', tradeCount: 'count', maxVolume: 'max', minVolume: 'min' }); } //# sourceMappingURL=mkd.utils.js.map