hft-js
Version:
High-Frequency Trading in Node.js
1,136 lines (1,135 loc) • 51.3 kB
JavaScript
"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 }));