hft-js
Version:
High-Frequency Trading in Node.js
296 lines • 13.6 kB
JavaScript
;
/*
* market.ts
*
* Copyright (c) 2025 Xiongfei Shi
*
* Author: Xiongfei Shi <xiongfei.shi(a)icloud.com>
* License: Apache-2.0
*
* https://github.com/shixiongfei/hft.js
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
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));
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createMarket = exports.Market = void 0;
var napi_ctp_1 = __importDefault(require("napi-ctp"));
var provider_js_1 = require("./provider.js");
var tape_js_1 = require("./tape.js");
var utils_js_1 = require("./utils.js");
var isValidPrice = function (x) { return x !== Number.MAX_VALUE && x !== 0; };
var isValidVolume = function (x) { return x !== Number.MAX_VALUE && x !== 0; };
var Market = /** @class */ (function (_super) {
__extends(Market, _super);
function Market(flowMdPath, frontMdAddrs) {
var _this = _super.call(this, flowMdPath, frontMdAddrs) || this;
_this.tradingDay = 0;
_this.recordings = new Set();
_this.symbols = new Map();
_this.lastTicks = new Map();
_this.subscribers = new Map();
return _this;
}
Market.prototype.hasRecorder = function () {
return !!this.recorder;
};
Market.prototype.setRecorder = function (receiver, symbols) {
this.recorder = receiver;
this.recorderSymbols = symbols;
};
Market.prototype.open = function (lifecycle) {
var _this = this;
if (this.marketApi) {
return true;
}
this.marketApi = napi_ctp_1.default.createMarketData(this.flowPath, this.frontAddrs);
this.marketApi.on(napi_ctp_1.default.MarketDataEvent.FrontConnected, function () {
_this._withRetry(function () { return _this.marketApi.reqUserLogin(); });
});
var fired = false;
this.marketApi.on(napi_ctp_1.default.MarketDataEvent.RspUserLogin, function (_, options) {
if (_this._isErrorResp(lifecycle, options, "login-error")) {
return;
}
var tradingDay = parseInt(_this.marketApi.getTradingDay());
if (_this.tradingDay !== tradingDay) {
_this.lastTicks.clear();
_this.tradingDay = tradingDay;
}
var instrumentIds = new Set(__spreadArray(__spreadArray([], Array.from(_this.recordings), true), Object.keys(_this.subscribers), true));
if (instrumentIds.size > 0) {
_this._withRetry(function () {
return _this.marketApi.subscribeMarketData(Array.from(instrumentIds));
});
}
if (!fired) {
fired = true;
lifecycle.onOpen();
}
});
this.marketApi.on(napi_ctp_1.default.MarketDataEvent.RtnDepthMarketData, function (depthMarketData) {
var instrumentId = depthMarketData.InstrumentID;
if (_this.recorder && _this.recordings.has(instrumentId)) {
_this.recorder.onMarketData(depthMarketData);
}
var symbol = _this.symbols.get(instrumentId);
if (!symbol) {
return;
}
var orderBook = {
asks: { price: [], volume: [] },
bids: { price: [], volume: [] },
};
if (isValidPrice(depthMarketData.AskPrice1) &&
isValidVolume(depthMarketData.AskVolume1)) {
orderBook.asks.price.push(depthMarketData.AskPrice1);
orderBook.asks.volume.push(depthMarketData.AskVolume1);
if (isValidPrice(depthMarketData.AskPrice2) &&
isValidVolume(depthMarketData.AskVolume2)) {
orderBook.asks.price.push(depthMarketData.AskPrice2);
orderBook.asks.volume.push(depthMarketData.AskVolume2);
if (isValidPrice(depthMarketData.AskPrice3) &&
isValidVolume(depthMarketData.AskVolume3)) {
orderBook.asks.price.push(depthMarketData.AskPrice3);
orderBook.asks.volume.push(depthMarketData.AskVolume3);
if (isValidPrice(depthMarketData.AskPrice4) &&
isValidVolume(depthMarketData.AskVolume4)) {
orderBook.asks.price.push(depthMarketData.AskPrice4);
orderBook.asks.volume.push(depthMarketData.AskVolume4);
if (isValidPrice(depthMarketData.AskPrice5) &&
isValidVolume(depthMarketData.AskVolume5)) {
orderBook.asks.price.push(depthMarketData.AskPrice5);
orderBook.asks.volume.push(depthMarketData.AskVolume5);
}
}
}
}
}
if (isValidPrice(depthMarketData.BidPrice1) &&
isValidVolume(depthMarketData.BidVolume1)) {
orderBook.bids.price.push(depthMarketData.BidPrice1);
orderBook.bids.volume.push(depthMarketData.BidVolume1);
if (isValidPrice(depthMarketData.BidPrice2) &&
isValidVolume(depthMarketData.BidVolume2)) {
orderBook.bids.price.push(depthMarketData.BidPrice2);
orderBook.bids.volume.push(depthMarketData.BidVolume2);
if (isValidPrice(depthMarketData.BidPrice3) &&
isValidVolume(depthMarketData.BidVolume3)) {
orderBook.bids.price.push(depthMarketData.BidPrice3);
orderBook.bids.volume.push(depthMarketData.BidVolume3);
if (isValidPrice(depthMarketData.BidPrice4) &&
isValidVolume(depthMarketData.BidVolume4)) {
orderBook.bids.price.push(depthMarketData.BidPrice4);
orderBook.bids.volume.push(depthMarketData.BidVolume4);
if (isValidPrice(depthMarketData.BidPrice5) &&
isValidVolume(depthMarketData.BidVolume5)) {
orderBook.bids.price.push(depthMarketData.BidPrice5);
orderBook.bids.volume.push(depthMarketData.BidVolume5);
}
}
}
}
}
var time = _this._parseTime(depthMarketData.UpdateTime);
var tick = Object.freeze({
symbol: symbol,
date: parseInt(depthMarketData.ActionDay),
time: time + depthMarketData.UpdateMillisec / 1000,
tradingDay: parseInt(depthMarketData.TradingDay),
preOpenInterest: depthMarketData.PreOpenInterest,
preClose: depthMarketData.PreClosePrice,
openInterest: depthMarketData.OpenInterest,
openPrice: depthMarketData.OpenPrice,
highPrice: depthMarketData.HighestPrice,
lowPrice: depthMarketData.LowestPrice,
lastPrice: depthMarketData.LastPrice,
volume: depthMarketData.Volume,
amount: depthMarketData.Turnover,
limits: Object.freeze({
upper: depthMarketData.UpperLimitPrice,
lower: depthMarketData.LowerLimitPrice,
}),
bandings: Object.freeze({
upper: depthMarketData.BandingUpperPrice,
lower: depthMarketData.BandingLowerPrice,
}),
orderBook: Object.freeze(orderBook),
});
var receivers = _this.subscribers.get(instrumentId);
if (receivers && receivers.length > 0) {
var lastTick = _this.lastTicks.get(instrumentId);
var tape_1 = (0, tape_js_1.calcTapeData)(tick, lastTick);
receivers.forEach(function (receiver) { return receiver.onTick(tick, tape_1); });
}
_this.lastTicks.set(instrumentId, tick);
});
return true;
};
Market.prototype.close = function (lifecycle) {
if (!this.marketApi) {
return;
}
this.marketApi.close();
this.marketApi = undefined;
lifecycle.onClose();
};
Market.prototype.startRecorder = function (instrument) {
var _this = this;
if (!this.recorderSymbols) {
return;
}
var symbols = this.recorderSymbols(instrument);
var instrumentIds = new Set();
symbols.forEach(function (symbol) {
var instrumentId = (0, utils_js_1.parseSymbol)(symbol)[0];
if (_this.recordings.has(instrumentId)) {
return;
}
_this.recordings.add(instrumentId);
if (!_this.subscribers.has(instrumentId)) {
_this.symbols.set(instrumentId, symbol);
instrumentIds.add(instrumentId);
}
});
if (instrumentIds.size > 0) {
this._withRetry(function () { var _a; return (_a = _this.marketApi) === null || _a === void 0 ? void 0 : _a.subscribeMarketData(Array.from(instrumentIds)); });
}
};
Market.prototype.stopRecorder = function () {
var _this = this;
if (this.recordings.size === 0) {
return;
}
var instrumentIds = new Set();
this.recordings.forEach(function (instrumentId) {
if (!_this.subscribers.has(instrumentId)) {
_this.symbols.delete(instrumentId);
instrumentIds.add(instrumentId);
}
});
this.recordings.clear();
if (instrumentIds.size > 0) {
this._withRetry(function () { var _a; return (_a = _this.marketApi) === null || _a === void 0 ? void 0 : _a.unsubscribeMarketData(Array.from(instrumentIds)); });
}
};
Market.prototype.subscribe = function (symbols, receiver) {
var _this = this;
var instrumentIds = new Set();
symbols.forEach(function (symbol) {
var instrumentId = (0, utils_js_1.parseSymbol)(symbol)[0];
var receivers = _this.subscribers.get(instrumentId);
if (receivers) {
if (!receivers.includes(receiver)) {
receivers.push(receiver);
}
}
else {
_this.subscribers.set(instrumentId, [receiver]);
if (!_this.recordings.has(instrumentId)) {
_this.symbols.set(instrumentId, symbol);
instrumentIds.add(instrumentId);
}
}
});
if (instrumentIds.size > 0) {
this._withRetry(function () { var _a; return (_a = _this.marketApi) === null || _a === void 0 ? void 0 : _a.subscribeMarketData(Array.from(instrumentIds)); });
}
};
Market.prototype.unsubscribe = function (symbols, receiver) {
var _this = this;
var instrumentIds = new Set();
symbols.forEach(function (symbol) {
var instrumentId = (0, utils_js_1.parseSymbol)(symbol)[0];
var receivers = _this.subscribers.get(instrumentId);
if (!receivers) {
return;
}
if (receivers.length > 0) {
var index = receivers.indexOf(receiver);
if (index < 0) {
return;
}
receivers.splice(index, 1);
}
if (receivers.length === 0) {
_this.subscribers.delete(instrumentId);
if (!_this.recordings.has(instrumentId)) {
_this.symbols.delete(instrumentId);
instrumentIds.add(instrumentId);
}
}
});
if (instrumentIds.size > 0) {
this._withRetry(function () { var _a; return (_a = _this.marketApi) === null || _a === void 0 ? void 0 : _a.unsubscribeMarketData(Array.from(instrumentIds)); });
}
};
return Market;
}(provider_js_1.CTPProvider));
exports.Market = Market;
var createMarket = function (flowMdPath, frontMdAddrs) { return new Market(flowMdPath, frontMdAddrs); };
exports.createMarket = createMarket;
//# sourceMappingURL=market.js.map