UNPKG

hft-js

Version:

High-Frequency Trading in Node.js

296 lines 13.6 kB
"use strict"; /* * 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