UNPKG

hft-js

Version:

High-Frequency Trading in Node.js

1,136 lines (1,135 loc) 51.3 kB
"use strict"; /* * trader.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 __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.createTrader = exports.Trader = void 0; var denque_1 = __importDefault(require("denque")); var napi_ctp_1 = __importDefault(require("napi-ctp")); var provider_js_1 = require("./provider.js"); var utils_js_1 = require("./utils.js"); var Trader = /** @class */ (function (_super) { __extends(Trader, _super); function Trader(flowTdPath, frontTdAddrs, userInfo) { var _this = _super.call(this, flowTdPath, frontTdAddrs) || this; _this.tradingDay = 0; _this.frontId = 0; _this.sessionId = 0; _this.orderRef = 0; _this.accountsQueryTime = 0; _this.positionDetailsChanged = true; _this.userInfo = userInfo; _this.receivers = []; _this.accounts = []; _this.positionDetails = []; _this.instruments = new Map(); _this.positions = new Map(); _this.orders = new Map(); _this.trades = new Map(); _this.marginRates = new Map(); _this.commRates = new Map(); _this.placeOrders = new Map(); _this.cancelOrders = new Map(); _this.orderStatistics = new Map(); _this.marginRatesQueue = new denque_1.default(); _this.commRatesQueue = new denque_1.default(); _this.accountsQueue = new denque_1.default(); _this.positionDetailsQueue = new denque_1.default(); return _this; } Trader.prototype.open = function (lifecycle) { var _this = this; if (this.traderApi) { return true; } this.traderApi = napi_ctp_1.default.createTrader(this.flowPath, this.frontAddrs); this.traderApi.on(napi_ctp_1.default.TraderEvent.FrontConnected, function () { _this._withRetry(function () { return _this.traderApi.reqAuthenticate(_this.userInfo); }); }); this.traderApi.on(napi_ctp_1.default.TraderEvent.FrontDisconnected, function () { _this.placeOrders.clear(); _this.cancelOrders.clear(); }); this.traderApi.on(napi_ctp_1.default.TraderEvent.RspAuthenticate, function (_, options) { if (_this._isErrorResp(lifecycle, options, "login-error")) { return; } _this._withRetry(function () { return _this.traderApi.reqUserLogin(_this.userInfo); }); }); this.traderApi.on(napi_ctp_1.default.TraderEvent.RspUserLogin, function (rspUserLogin, options) { if (_this._isErrorResp(lifecycle, options, "login-error")) { return; } _this.frontId = rspUserLogin.FrontID; _this.sessionId = rspUserLogin.SessionID; _this.orderRef = parseInt(rspUserLogin.MaxOrderRef); var tradingDay = parseInt(_this.traderApi.getTradingDay()); if (_this.tradingDay !== tradingDay) { _this.marginRates.clear(); _this.commRates.clear(); _this.orderStatistics.clear(); _this.tradingDay = tradingDay; } _this._withRetry(function () { return _this.traderApi.reqSettlementInfoConfirm(_this.userInfo); }); }); this.traderApi.on(napi_ctp_1.default.TraderEvent.RspSettlementInfoConfirm, function (_, options) { if (_this._isErrorResp(lifecycle, options, "login-error")) { return; } _this.orders.clear(); _this._withRetry(function () { return _this.traderApi.reqQryOrder(_this.userInfo); }); }); this.traderApi.on(napi_ctp_1.default.TraderEvent.RspQryOrder, function (order, options) { if (_this._isErrorResp(lifecycle, options, "query-order-error")) { return; } if (order) { var orderId = _this._calcOrderId(order); _this.orders.set(orderId, order); } if (options.isLast) { _this.trades.clear(); _this._withRetry(function () { return _this.traderApi.reqQryTrade(_this.userInfo); }); } }); this.traderApi.on(napi_ctp_1.default.TraderEvent.RspQryTrade, function (trade, options) { if (_this._isErrorResp(lifecycle, options, "query-trade-error")) { return; } if (trade) { var orderId = _this._calcOrderId(trade); var trades = _this.trades.get(orderId); if (trades) { trades.push(trade); } else { _this.trades.set(orderId, [trade]); } } if (options.isLast) { _this.instruments.clear(); _this._withRetry(function () { return _this.traderApi.reqQryInstrument(); }); } }); this.traderApi.on(napi_ctp_1.default.TraderEvent.RspQryInstrument, function (instrument, options) { if (_this._isErrorResp(lifecycle, options, "query-instrument-error")) { return; } if (instrument) { if (instrument.ProductClass === napi_ctp_1.default.ProductClassType.Futures || instrument.ProductClass === napi_ctp_1.default.ProductClassType.Options) { _this.instruments.set(instrument.InstrumentID, instrument); } } if (options.isLast) { _this.positions.clear(); _this._withRetry(function () { return _this.traderApi.reqQryInvestorPosition(_this.userInfo); }); } }); var fired = false; this.traderApi.on(napi_ctp_1.default.TraderEvent.RspQryInvestorPosition, function (position, options) { if (_this._isErrorResp(lifecycle, options, "query-positions-error")) { return; } if (position) { var symbol = _this._toSymbol(position.InstrumentID); if (symbol) { var posInfo = _this._ensurePositionInfo(symbol); var ExchangeSH = ["SHFE", "INE"]; switch (position.PosiDirection) { case napi_ctp_1.default.PosiDirectionType.Long: if (position.PositionDate === napi_ctp_1.default.PositionDateType.Today) { if (ExchangeSH.includes(position.ExchangeID)) { posInfo.today.long.position += position.TodayPosition; } else { posInfo.today.long.position += position.Position; } } else { posInfo.history.long.position += position.Position - position.TodayPosition; } break; case napi_ctp_1.default.PosiDirectionType.Short: if (position.PositionDate === napi_ctp_1.default.PositionDateType.Today) { if (ExchangeSH.includes(position.ExchangeID)) { posInfo.today.short.position += position.TodayPosition; } else { posInfo.today.short.position += position.Position; } } else { posInfo.history.short.position += position.Position - position.TodayPosition; } break; } } } if (options.isLast) { if (!fired) { fired = true; lifecycle.onOpen(); } if (_this.accountsQueue.size() > 0) { _this._withRetry(function () { return _this.traderApi.reqQryTradingAccount(_this.userInfo); }); } if (_this.positionDetailsQueue.size() > 0) { _this._withRetry(function () { return _this.traderApi.reqQryInvestorPositionDetail(_this.userInfo); }); } _this._processMarginRatesQueue(); _this._processCommissionRatesQueue(); } }); this.traderApi.on(napi_ctp_1.default.TraderEvent.RtnOrder, function (order) { var orderId = _this._calcOrderId(order); var current = _this.orders.get(orderId); if (current) { if (order.OrderSubmitStatus === current.OrderSubmitStatus && order.OrderStatus === current.OrderStatus) { return; } } _this.orders.set(orderId, order); switch (_this._calcOrderStatus(order)) { case "submitted": { var orderData_1 = _this._toOrderData(order); var symbol = _this._toSymbol(order.InstrumentID); if (symbol) { if (orderData_1.offset === "open") { _this._recordPending(symbol, orderData_1.side, orderData_1.offset, orderData_1.volume); } else { _this._freezePosition(symbol, orderData_1.side, orderData_1.offset, orderData_1.volume); } var statistic = _this._ensureOrderStatistic(symbol); statistic.entrusts += 1; } _this.receivers.forEach(function (receiver) { return receiver.onEntrust(orderData_1); }); } break; case "filled": { var symbol = _this._toSymbol(order.InstrumentID); if (symbol) { var statistic = _this._ensureOrderStatistic(symbol); statistic.filleds += 1; } } break; case "canceled": { var orderData_2 = _this._toOrderData(order); var symbol = _this._toSymbol(order.InstrumentID); if (symbol) { if (orderData_2.offset === "open") { _this._recoverPending(symbol, orderData_2.side, orderData_2.offset, orderData_2.volume); } else { _this._unfreezePosition(symbol, orderData_2.side, orderData_2.offset, orderData_2.volume); } var statistic = _this._ensureOrderStatistic(symbol); statistic.cancels += 1; } _this.receivers.forEach(function (receiver) { return receiver.onCancel(orderData_2); }); } break; case "rejected": { var orderData_3 = _this._toOrderData(order); var symbol = _this._toSymbol(order.InstrumentID); if (symbol) { var statistic = _this._ensureOrderStatistic(symbol); statistic.rejects += 1; } _this.receivers.forEach(function (receiver) { return receiver.onReject(orderData_3); }); } break; } }); this.traderApi.on(napi_ctp_1.default.TraderEvent.RtnTrade, function (trade) { var orderId = _this._calcOrderId(trade); var trades = _this.trades.get(orderId); if (trades) { trades.push(trade); } else { _this.trades.set(orderId, [trade]); } _this.positionDetailsChanged = true; var order = _this.orders.get(orderId); if (order) { var orderData_4 = _this._toOrderData(order); var tradeData_1 = _this._toTradeData(trade); var symbol = _this._toSymbol(order.InstrumentID); if (symbol) { _this._calcPosition(symbol, orderData_4.side, orderData_4.offset, tradeData_1.volume); } _this.receivers.forEach(function (receiver) { return receiver.onTrade(orderData_4, tradeData_1); }); } }); this.traderApi.on(napi_ctp_1.default.TraderEvent.RspQryInstrumentMarginRate, function (marginRate, options) { var query = _this.marginRatesQueue.shift(); if (_this._isErrorResp(lifecycle, options, "query-margin-rate-error")) { if (query) { query.receiver.onMarginRate(undefined); } return; } if (marginRate) { _this.marginRates.set(marginRate.InstrumentID, marginRate); if (query) { query.receiver.onMarginRate(_this._toMarginRate(query.symbol, marginRate)); } } _this._processMarginRatesQueue(); }); this.traderApi.on(napi_ctp_1.default.TraderEvent.RspQryInstrumentCommissionRate, function (commRate, options) { var query = _this.commRatesQueue.shift(); if (_this._isErrorResp(lifecycle, options, "query-commission-rate-error")) { if (query) { query.receiver.onCommissionRate(undefined); } return; } if (commRate) { _this.commRates.set(commRate.InstrumentID, commRate); if (query) { query.receiver.onCommissionRate(_this._toCommissionRate(query.symbol, commRate)); } } _this._processCommissionRatesQueue(); }); this.traderApi.on(napi_ctp_1.default.TraderEvent.RspQryTradingAccount, function (account, options) { if (_this._isErrorResp(lifecycle, options, "query-accounts-error")) { var receivers = _this.accountsQueue.toArray(); receivers.forEach(function (receiver) { return receiver.onTradingAccounts(undefined); }); _this.accountsQueue.clear(); return; } if (account) { _this.accounts.push(account); } if (options.isLast) { var accounts_1 = _this.accounts.map(_this._toTradingAccount, _this); var receivers = _this.accountsQueue.toArray(); receivers.forEach(function (receiver) { return receiver.onTradingAccounts(accounts_1); }); _this.accountsQueue.clear(); _this.accountsQueryTime = Date.now(); } }); this.traderApi.on(napi_ctp_1.default.TraderEvent.RspQryInvestorPositionDetail, function (positionDetail, options) { if (_this._isErrorResp(lifecycle, options, "query-position-details-error")) { var receivers = _this.positionDetailsQueue.toArray(); receivers.forEach(function (receiver) { return receiver.onPositionDetails(undefined); }); _this.positionDetailsQueue.clear(); return; } if (positionDetail) { _this.positionDetails.push(positionDetail); } if (options.isLast) { var positionDetails_1 = _this.positionDetails.map(_this._toPositionDetail, _this); var receivers = _this.positionDetailsQueue.toArray(); _this.positionDetailsChanged = false; receivers.forEach(function (receiver) { return receiver.onPositionDetails(positionDetails_1); }); _this.positionDetailsQueue.clear(); } }); this.traderApi.on(napi_ctp_1.default.TraderEvent.RspOrderInsert, function (order, options) { if (options.rspInfo && order && options.requestId && options.isLast) { var receiver = _this.placeOrders.get(options.requestId); if (receiver) { _this.placeOrders.delete(options.requestId); receiver.onPlaceOrderError("".concat(options.rspInfo.ErrorID, ": ").concat(options.rspInfo.ErrorMsg)); } } }); this.traderApi.on(napi_ctp_1.default.TraderEvent.RspOrderAction, function (order, options) { if (options.rspInfo && order && options.requestId && options.isLast) { var receiver = _this.cancelOrders.get(options.requestId); if (receiver) { _this.cancelOrders.delete(options.requestId); receiver.onCancelOrderError("".concat(options.rspInfo.ErrorID, ": ").concat(options.rspInfo.ErrorMsg)); } } }); return true; }; Trader.prototype.close = function (lifecycle) { if (!this.traderApi) { return; } this.traderApi.close(); this.traderApi = undefined; lifecycle.onClose(); }; Trader.prototype.addReceiver = function (receiver) { if (!this.receivers.includes(receiver)) { this.receivers.push(receiver); } }; Trader.prototype.removeReceiver = function (receiver) { var index = this.receivers.indexOf(receiver); if (index < 0) { return; } this.receivers.splice(index, 1); }; Trader.prototype.getTradingDay = function () { return this.tradingDay; }; Trader.prototype.getOrderStatistics = function () { var statistics = Array.from(this.orderStatistics.values()); return statistics.map(function (stat) { return Object.freeze(__assign({}, stat)); }); }; Trader.prototype.getOrderStatistic = function (symbol) { var statistic = this.orderStatistics.get(symbol); if (!statistic) { return Object.freeze({ symbol: symbol, places: 0, entrusts: 0, filleds: 0, cancels: 0, rejects: 0, }); } return Object.freeze(__assign({}, statistic)); }; Trader.prototype.queryCommissionRate = function (symbol, receiver) { var _this = this; var instrumentId = (0, utils_js_1.parseSymbol)(symbol)[0]; var commRate = this.commRates.get(instrumentId); if (commRate) { receiver.onCommissionRate(this._toCommissionRate(symbol, commRate)); return; } this.commRatesQueue.push({ symbol: symbol, receiver: receiver }); if (this.commRatesQueue.size() === 1) { this._withRetry(function () { var _a; return (_a = _this.traderApi) === null || _a === void 0 ? void 0 : _a.reqQryInstrumentCommissionRate(__assign(__assign({}, _this.userInfo), { InstrumentID: instrumentId })); }); } }; Trader.prototype.queryMarginRate = function (symbol, receiver) { var _this = this; var instrumentId = (0, utils_js_1.parseSymbol)(symbol)[0]; var marginRate = this.marginRates.get(instrumentId); if (marginRate) { receiver.onMarginRate(this._toMarginRate(symbol, marginRate)); return; } this.marginRatesQueue.push({ symbol: symbol, receiver: receiver }); if (this.marginRatesQueue.size() === 1) { this._withRetry(function () { var _a; return (_a = _this.traderApi) === null || _a === void 0 ? void 0 : _a.reqQryInstrumentMarginRate(__assign(__assign({}, _this.userInfo), { HedgeFlag: napi_ctp_1.default.HedgeFlagType.Speculation, InstrumentID: instrumentId })); }); } }; Trader.prototype.queryInstrument = function (symbol, receiver) { var _a = (0, utils_js_1.parseSymbol)(symbol), instrumentId = _a[0], exchangeId = _a[1]; var instrument = this.instruments.get(instrumentId); receiver.onInstrument(instrument && instrument.ExchangeID === exchangeId ? this._toInstrumentData(instrument) : undefined); }; Trader.prototype.queryPosition = function (symbol, receiver) { var position = this.positions.get(symbol); if (position) { receiver.onPosition(this._toPositionData(position)); return; } var instrumentId = (0, utils_js_1.parseSymbol)(symbol)[0]; if (!this.instruments.has(instrumentId)) { receiver.onPosition(undefined); return; } receiver.onPosition(Object.freeze({ symbol: symbol, today: Object.freeze({ long: Object.freeze({ position: 0, frozen: 0 }), short: Object.freeze({ position: 0, frozen: 0 }), }), history: Object.freeze({ long: Object.freeze({ position: 0, frozen: 0 }), short: Object.freeze({ position: 0, frozen: 0 }), }), pending: Object.freeze({ long: 0, short: 0 }), })); }; Trader.prototype.queryInstruments = function (receiver, type) { var instruments = Array.from(this.instruments.values()); switch (type) { case "futures": receiver.onInstruments(instruments .filter(function (instrument) { return instrument.ProductClass === napi_ctp_1.default.ProductClassType.Futures; }) .map(this._toInstrumentData, this)); break; case "options": receiver.onInstruments(instruments .filter(function (instrument) { return instrument.ProductClass === napi_ctp_1.default.ProductClassType.Options; }) .map(this._toInstrumentData, this)); break; default: receiver.onInstruments(instruments.map(this._toInstrumentData, this)); break; } }; Trader.prototype.queryTradingAccounts = function (receiver) { var _this = this; if (this.accountsQueue.size() > 0) { this.accountsQueue.push(receiver); return; } var elapsed = Date.now() - this.accountsQueryTime; if (elapsed < 3000) { receiver.onTradingAccounts(this.accounts.map(this._toTradingAccount, this)); return; } this.accountsQueue.push(receiver); this.accounts.splice(0, this.accounts.length); this._withRetry(function () { var _a; return (_a = _this.traderApi) === null || _a === void 0 ? void 0 : _a.reqQryTradingAccount(_this.userInfo); }); }; Trader.prototype.queryPositionDetails = function (receiver) { var _this = this; if (this.positionDetailsQueue.size() > 0) { this.positionDetailsQueue.push(receiver); return; } if (!this.positionDetailsChanged) { receiver.onPositionDetails(this.positionDetails.map(this._toPositionDetail, this)); return; } this.positionDetailsQueue.push(receiver); this.positionDetails.splice(0, this.positionDetails.length); this._withRetry(function () { var _a; return (_a = _this.traderApi) === null || _a === void 0 ? void 0 : _a.reqQryInvestorPositionDetail(_this.userInfo); }); }; Trader.prototype.queryPositions = function (receiver) { var _this = this; var positions = []; this.positions.forEach(function (position) { return positions.push(_this._toPositionData(position)); }); receiver.onPositions(positions); }; Trader.prototype.queryOrders = function (receiver) { var _this = this; var orders = []; this.orders.forEach(function (order) { orders.push(_this._toOrderData(order)); }); receiver.onOrders(orders); }; Trader.prototype.placeOrder = function (symbol, offset, side, volume, price, flag, receiver) { var _this = this; if (flag !== "limit") { receiver.onPlaceOrderError("Only Supports Limit Order"); return; } var instrumentId = (0, utils_js_1.parseSymbol)(symbol)[0]; var instrument = this.instruments.get(instrumentId); if (!instrument) { receiver.onPlaceOrderError("Instrument Not Found"); return; } var orderRef = 0; this._withRetry(function () { var _a; orderRef = ++_this.orderRef; return (_a = _this.traderApi) === null || _a === void 0 ? void 0 : _a.reqOrderInsert(__assign(__assign({}, _this.userInfo), { OrderRef: "".concat(orderRef), InstrumentID: instrumentId, ExchangeID: instrument.ExchangeID, LimitPrice: price, VolumeTotalOriginal: volume, VolumeCondition: napi_ctp_1.default.VolumeConditionType.AV, TimeCondition: napi_ctp_1.default.TimeConditionType.GFD, Direction: _this._toDirection(side), OrderPriceType: napi_ctp_1.default.OrderPriceTypeType.LimitPrice, CombOffsetFlag: _this._toOffsetFlag(offset), CombHedgeFlag: napi_ctp_1.default.HedgeFlagType.Speculation, ContingentCondition: napi_ctp_1.default.ContingentConditionType.Immediately, ForceCloseReason: napi_ctp_1.default.ForceCloseReasonType.NotForceClose, IsAutoSuspend: 0, UserForceClose: 0 })); }).then(function (requestId) { if (!requestId) { receiver.onPlaceOrderError("Request Failed"); return; } if (requestId < 0) { receiver.onPlaceOrderError("Request Error"); return; } var statistic = _this._ensureOrderStatistic(symbol); statistic.places += 1; _this.placeOrders.set(requestId, receiver); var receiptId = "".concat(_this.frontId, ":").concat(_this.sessionId, ":").concat(orderRef); receiver.onPlaceOrderSent(receiptId); return receiptId; }); }; Trader.prototype.cancelOrder = function (order, receiver) { var _this = this; var current = this.orders.get(order.id); if (!current) { receiver.onCancelOrderError("Order Not Found"); return; } if (order.cancelTime) { receiver.onCancelOrderError("Already Canceled"); return; } this._withRetry(function () { var _a; return (_a = _this.traderApi) === null || _a === void 0 ? void 0 : _a.reqOrderAction(__assign(__assign({}, _this.userInfo), { InstrumentID: current.InstrumentID, FrontID: current.FrontID, SessionID: current.SessionID, OrderRef: current.OrderRef, ExchangeID: current.ExchangeID, OrderSysID: current.OrderSysID, ActionFlag: napi_ctp_1.default.ActionFlagType.Delete })); }).then(function (requestId) { if (!requestId) { receiver.onCancelOrderError("Request Failed"); return; } if (requestId < 0) { receiver.onCancelOrderError("Request Error"); return; } _this.cancelOrders.set(requestId, receiver); receiver.onCancelOrderSent(); }); }; Trader.prototype._toSymbol = function (instrumentId) { var instrument = this.instruments.get(instrumentId); if (!instrument) { return undefined; } return "".concat(instrument.InstrumentID, ".").concat(instrument.ExchangeID); }; Trader.prototype._calcOrderId = function (orderOrTrade) { var ExchangeID = orderOrTrade.ExchangeID, TraderID = orderOrTrade.TraderID, OrderLocalID = orderOrTrade.OrderLocalID; return "".concat(ExchangeID, ":").concat(TraderID, ":").concat(OrderLocalID); }; Trader.prototype._calcReceiptId = function (order) { return "".concat(order.FrontID, ":").concat(order.SessionID, ":").concat(parseInt(order.OrderRef)); }; Trader.prototype._calcOrderStatus = function (order, traded) { switch (order.OrderStatus) { case napi_ctp_1.default.OrderStatusType.Unknown: return "submitted"; case napi_ctp_1.default.OrderStatusType.AllTraded: return "filled"; case napi_ctp_1.default.OrderStatusType.Canceled: switch (order.OrderSubmitStatus) { case napi_ctp_1.default.OrderSubmitStatusType.InsertRejected: case napi_ctp_1.default.OrderSubmitStatusType.CancelRejected: case napi_ctp_1.default.OrderSubmitStatusType.ModifyRejected: return "rejected"; default: return "canceled"; } default: return traded && order.VolumeTotalOriginal === traded ? "filled" : "partially-filled"; } }; Trader.prototype._calcOrderFlag = function (orderPriceType) { switch (orderPriceType) { case napi_ctp_1.default.OrderPriceTypeType.LimitPrice: return "limit"; default: return "market"; } }; Trader.prototype._calcSideType = function (direction) { switch (direction) { case napi_ctp_1.default.DirectionType.Buy: return "long"; case napi_ctp_1.default.DirectionType.Sell: return "short"; } }; Trader.prototype._toDirection = function (side) { switch (side) { case "long": return napi_ctp_1.default.DirectionType.Buy; case "short": return napi_ctp_1.default.DirectionType.Sell; } }; Trader.prototype._calcOffsetType = function (offset) { switch (offset) { case napi_ctp_1.default.OffsetFlagType.Open: return "open"; case napi_ctp_1.default.OffsetFlagType.CloseToday: return "close-today"; default: return "close"; } }; Trader.prototype._toOffsetFlag = function (offset) { switch (offset) { case "open": return napi_ctp_1.default.OffsetFlagType.Open; case "close": return napi_ctp_1.default.OffsetFlagType.Close; case "close-today": return napi_ctp_1.default.OffsetFlagType.CloseToday; } }; Trader.prototype._calcProductType = function (productClass) { switch (productClass) { case napi_ctp_1.default.ProductClassType.Futures: return "futures"; case napi_ctp_1.default.ProductClassType.Options: return "options"; default: throw new Error("Unsupported product class: ".concat(productClass)); } }; Trader.prototype._ensurePositionInfo = function (symbol) { var position = this.positions.get(symbol); if (!position) { position = { symbol: symbol, today: { long: { position: 0, frozen: 0 }, short: { position: 0, frozen: 0 }, }, history: { long: { position: 0, frozen: 0 }, short: { position: 0, frozen: 0 }, }, pending: { long: 0, short: 0 }, }; this.positions.set(symbol, position); } return position; }; Trader.prototype._ensureOrderStatistic = function (symbol) { var statistic = this.orderStatistics.get(symbol); if (!statistic) { statistic = { symbol: symbol, places: 0, entrusts: 0, filleds: 0, cancels: 0, rejects: 0, }; this.orderStatistics.set(symbol, statistic); } return statistic; }; Trader.prototype._calcPosition = function (symbol, side, offset, volume) { var position = this._ensurePositionInfo(symbol); switch (offset) { case "open": switch (side) { case "long": position.today.long.position += volume; if (position.pending.long >= volume) { position.pending.long -= volume; } else { position.pending.long = 0; } break; case "short": position.today.short.position += volume; if (position.pending.short >= volume) { position.pending.short -= volume; } else { position.pending.short = 0; } break; } break; case "close": switch (side) { case "long": if (position.history.short.position >= volume) { position.history.short.position -= volume; } else { var rest = volume - position.history.short.position; position.history.short.position -= position.history.short.position; if (rest > 0) { if (position.today.short.position >= rest) { position.today.short.position -= rest; } else { position.today.short.position = 0; } } } if (position.history.short.frozen >= volume) { position.history.short.frozen -= volume; } else { var rest = volume - position.history.short.frozen; position.history.short.frozen -= position.history.short.frozen; if (rest > 0) { if (position.today.short.frozen >= rest) { position.today.short.frozen -= rest; } else { position.today.short.frozen = 0; } } } break; case "short": if (position.history.long.position >= volume) { position.history.long.position -= volume; } else { var rest = volume - position.history.long.position; position.history.long.position -= position.history.long.position; if (rest > 0) { if (position.today.long.position >= rest) { position.today.long.position -= rest; } else { position.today.long.position = 0; } } } if (position.history.long.frozen >= volume) { position.history.long.frozen -= volume; } else { var rest = volume - position.history.long.frozen; position.history.long.frozen -= position.history.long.frozen; if (rest > 0) { if (position.today.long.frozen >= rest) { position.today.long.frozen -= rest; } else { position.today.long.frozen = 0; } } } break; } break; case "close-today": switch (side) { case "long": if (position.today.short.position >= volume) { position.today.short.position -= volume; } else { position.today.short.position = 0; } if (position.today.short.frozen >= volume) { position.today.short.frozen -= volume; } else { position.today.short.frozen = 0; } break; case "short": if (position.today.long.position >= volume) { position.today.long.position -= volume; } else { position.today.long.position = 0; } if (position.today.long.frozen >= volume) { position.today.long.frozen -= volume; } else { position.today.long.frozen = 0; } break; } break; } }; Trader.prototype._recordPending = function (symbol, side, offset, volume) { if (offset !== "open") { return; } var position = this._ensurePositionInfo(symbol); switch (side) { case "long": position.pending.long += volume; break; case "short": position.pending.short += volume; break; } }; Trader.prototype._recoverPending = function (symbol, side, offset, volume) { if (offset !== "open") { return; } var position = this.positions.get(symbol); if (!position) { return; } switch (side) { case "long": position.pending.long -= volume; break; case "short": position.pending.short -= volume; break; } }; Trader.prototype._freezePosition = function (symbol, side, offset, volume) { var position = this.positions.get(symbol); if (!position) { return; } switch (offset) { case "close": switch (side) { case "long": position.history.short.frozen += volume; break; case "short": position.history.long.frozen += volume; break; } break; case "close-today": switch (side) { case "long": position.today.short.frozen += volume; break; case "short": position.today.long.frozen += volume; break; } break; } }; Trader.prototype._unfreezePosition = function (symbol, side, offset, volume) { var position = this.positions.get(symbol); if (!position) { return; } switch (offset) { case "close": switch (side) { case "long": if (position.history.short.frozen >= volume) { position.history.short.frozen -= volume; } else { position.history.short.frozen = 0; } break; case "short": if (position.history.long.frozen >= volume) { position.history.long.frozen -= volume; } else { position.history.long.frozen = 0; } break; } break; case "close-today": switch (side) { case "long": if (position.today.short.frozen >= volume) { position.today.short.frozen -= volume; } else { position.today.short.frozen = 0; } break; case "short": if (position.today.long.frozen >= volume) { position.today.long.frozen -= volume; } else { position.today.long.frozen = 0; } break; } break; } }; Trader.prototype._toTradeData = function (trade) { return Object.freeze({ id: trade.TradeID, date: parseInt(trade.TradeDate), time: this._parseTime(trade.TradeTime), price: trade.Price, volume: trade.Volume, }); }; Trader.prototype._toOrderData = function (order) { var _a; var orderId = this._calcOrderId(order); var trades = (_a = this.trades.get(orderId)) !== null && _a !== void 0 ? _a : []; var traded = trades .map(function (trade) { return trade.Volume; }) .reduce(function (a, b) { return a + b; }, 0); return Object.freeze({ id: orderId, receiptId: this._calcReceiptId(order), symbol: "".concat(order.InstrumentID, ".").concat(order.ExchangeID), date: parseInt(order.InsertDate), time: this._parseTime(order.InsertTime), flag: this._calcOrderFlag(order.OrderPriceType), side: this._calcSideType(order.Direction), offset: this._calcOffsetType(order.CombOffsetFlag), price: order.LimitPrice, volume: order.VolumeTotalOriginal, traded: traded, status: this._calcOrderStatus(order, traded), trades: trades.map(this._toTradeData, this), cancelTime: order.CancelTime !== "" ? this._parseTime(order.CancelTime) : undefined, }); }; Trader.prototype._toInstrumentData = function (instrument) { return Object.freeze({ symbol: "".concat(instrument.InstrumentID, ".").concat(instrument.ExchangeID), id: instrument.InstrumentID, name: instrument.InstrumentName, exchangeId: instrument.ExchangeID, productId: instrument.ProductID, productType: this._calcProductType(instrument.ProductClass), deliveryTime: instrument.DeliveryYear * 100 + instrument.DeliveryMonth, createDate: parseInt(instrument.CreateDate), openDate: parseInt(instrument.OpenDate), expireDate: parseInt(instrument.ExpireDate), multiple: instrument.VolumeMultiple, priceTick: instrument.PriceTick, maxLimitOrderVolume: instrument.MaxLimitOrderVolume, minLimitOrderVolume: instrument.MinLimitOrderVolume, }); }; Trader.prototype._toCommissionRate = function (symbol, commRate) { return Object.freeze({ symbol: symbol, open: Object.freeze({ ratio: commRate.OpenRatioByMoney, amount: commRate.OpenRatioByVolume, }), close: Object.freeze({ ratio: commRate.CloseRatioByMoney, amount: commRate.CloseRatioByVolume, }), closeToday: Object.freeze({ ratio: commRate.CloseTodayRatioByMoney, amount: commRate.CloseTodayRatioByVolume, }), }); }; Trader.prototype._toMarginRate = function (symbol, marginRate) { return Object.freeze({ symbol: symbol, long: Object.freeze({ ratio: marginRate.LongMarginRatioByMoney, amount: marginRate.LongMarginRatioByVolume, }), short: Object.freeze({ ratio: marginRate.ShortMarginRatioByMoney, amount: marginRate.ShortMarginRatioByVolume, }), }); }; Trader.prototype._toTradingAccount = function (account) { return Object.freeze({ id: account.AccountID, currency: account.CurrencyID, preBalance: account.PreBalance - account.Withdraw + account.Deposit, balance: account.Balance, cash: account.Available, margin: account.CurrMargin, commission: account.Commission, frozenMargin: account.FrozenMargin, frozenCash: account.FrozenCash, frozenCommission: account.FrozenCommission, }); }; Trader.prototype._toPositionDetail = function (positionDetail) { return Object.freeze({ symbol: this._toSymbol(positionDetail.InstrumentID), date: parseInt(positionDetail.OpenDate), side: this._calcSideType(positionDetail.Direction), price: positionDetail.OpenPrice, volume: positionDetail.Volume, margin: positionDetail.Margin, }); }; Trader.prototype._toPositionData = function (position) { return Object.freeze({ symbol: position.symbol, today: Object.freeze({ long: Object.freeze(__assign({}, position.today.long)), short: Object.freeze(__assign({}, position.today.short)), }), history: Object.freeze({ long: Object.freeze(__assign({}, position.history.long)), short: Object.freeze(__assign({}, position.history.short)), }), pending: Object.freeze(__assign({}, position.pending)), }); }; Trader.prototype._processMarginRatesQueue = function () { var _this = this; var _loop_1 = function () { var nextQuery = this_1.marginRatesQueue.peekFront(); var instrumentId = (0, utils_js_1.parseSymbol)(nextQuery.symbol)[0]; var marginRate = this_1.marginRates.get(instrumentId); if (marginRate) { nextQuery.receiver.onMarginRate(this_1._toMarginRate(nextQuery.symbol, marginRate)); this_1.marginRatesQueue.shift(); } else { this_1._withRetry(function () { return _this.traderApi.reqQryInstrumentMarginRate(__assign(__assign({}, _this.userInfo), { HedgeFlag: napi_ctp_1.default.HedgeFlagType.Speculation, InstrumentID: instrumentId }));