@stoqey/ibkr
Version:
NodeJS Interactive Brokers wrapper & utilities using @stoqey/ib
202 lines • 9.22 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 __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