hft-js 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/market.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * market.ts
3
3
  *
4
- * Copyright (c) 2025 Xiongfei Shi
4
+ * Copyright (c) 2025-2026 Xiongfei Shi
5
5
  *
6
6
  * Author: Xiongfei Shi <xiongfei.shi(a)icloud.com>
7
7
  * License: Apache-2.0
@@ -20,6 +20,7 @@ import type { InstrumentData, OrderBook, TickData } from "./typedef.js";
20
20
  import { isValidPrice, isValidVolume, parseSymbol } from "./utils.js";
21
21
  import { calcTapeData } from "./tape.js";
22
22
  import type {
23
+ IErrorReceiver,
23
24
  ILifecycleListener,
24
25
  IMarketProvider,
25
26
  IMarketRecorderProvider,
@@ -28,6 +29,8 @@ import type {
28
29
  ITickReceiver,
29
30
  } from "./interfaces.js";
30
31
 
32
+ type TickDataRaw = { tick: TickData; raw: DepthMarketDataField };
33
+
31
34
  export interface IMarketListener {
32
35
  onSubscribed: (symbol: string) => void;
33
36
  onUnsubscribed: (symbol: string) => void;
@@ -48,7 +51,7 @@ export class Market
48
51
  private readonly listener?: IMarketListener;
49
52
  private readonly recordings: Set<string>;
50
53
  private readonly symbols: Map<string, string>;
51
- private readonly lastTicks: Map<string, TickData>;
54
+ private readonly lastTicks: Map<string, TickDataRaw>;
52
55
  private readonly subscribers: Map<string, ITickReceiver[]>;
53
56
 
54
57
  constructor(
@@ -68,7 +71,11 @@ export class Market
68
71
  }
69
72
  }
70
73
 
71
- getRecorder() {
74
+ getDepthMarketDataRaw() {
75
+ return Array.from(this.lastTicks.values().map((lastTick) => lastTick.raw));
76
+ }
77
+
78
+ getRecorderProvider() {
72
79
  return this;
73
80
  }
74
81
 
@@ -85,10 +92,11 @@ export class Market
85
92
  }
86
93
 
87
94
  getLastTick(instrumentId: string) {
88
- return this.lastTicks.get(instrumentId);
95
+ const lastTick = this.lastTicks.get(instrumentId);
96
+ return lastTick ? lastTick.tick : undefined;
89
97
  }
90
98
 
91
- open(lifecycle: ILifecycleListener) {
99
+ open(lifecycle: ILifecycleListener, errorReceiver: IErrorReceiver) {
92
100
  if (this.marketApi) {
93
101
  return true;
94
102
  }
@@ -104,7 +112,7 @@ export class Market
104
112
  this.marketApi.on<RspUserLoginField>(
105
113
  ctp.MarketDataEvent.RspUserLogin,
106
114
  (_, options) => {
107
- if (this._isErrorResp(lifecycle, options, "login-error")) {
115
+ if (this._isErrorResp(errorReceiver, options, "login-error")) {
108
116
  return;
109
117
  }
110
118
 
@@ -286,13 +294,13 @@ export class Market
286
294
  orderBook: Object.freeze(orderBook),
287
295
  });
288
296
 
289
- const lastTick = this.lastTicks.get(instrumentId);
290
- const receivers = this.subscribers.get(instrumentId);
297
+ this.lastTicks.set(instrumentId, { tick, raw: depthMarketData });
291
298
 
292
- this.lastTicks.set(instrumentId, tick);
299
+ const receivers = this.subscribers.get(instrumentId);
293
300
 
294
301
  if (receivers && receivers.length > 0) {
295
- const tape = calcTapeData(tick, lastTick);
302
+ const lastTick = this.lastTicks.get(instrumentId);
303
+ const tape = calcTapeData(tick, lastTick ? lastTick.tick : undefined);
296
304
  receivers.forEach((receiver) => receiver.onTick(tick, tape));
297
305
  }
298
306
  },
@@ -301,7 +309,7 @@ export class Market
301
309
  return true;
302
310
  }
303
311
 
304
- close(lifecycle: ILifecycleListener) {
312
+ close(lifecycle: ILifecycleListener, _errorReceiver: IErrorReceiver) {
305
313
  if (!this.marketApi) {
306
314
  return;
307
315
  }
@@ -340,6 +348,8 @@ export class Market
340
348
  this.marketApi?.subscribeMarketData(Array.from(instrumentIds)),
341
349
  );
342
350
  }
351
+
352
+ this.recorder?.onOpen();
343
353
  }
344
354
 
345
355
  stopRecorder() {
@@ -363,6 +373,8 @@ export class Market
363
373
  this.marketApi?.unsubscribeMarketData(Array.from(instrumentIds)),
364
374
  );
365
375
  }
376
+
377
+ this.recorder?.onClose();
366
378
  }
367
379
 
368
380
  subscribe(symbols: string[], receiver: ITickReceiver) {
package/src/provider.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * provider.ts
3
3
  *
4
- * Copyright (c) 2025 Xiongfei Shi
4
+ * Copyright (c) 2025-2026 Xiongfei Shi
5
5
  *
6
6
  * Author: Xiongfei Shi <xiongfei.shi(a)icloud.com>
7
7
  * License: Apache-2.0
@@ -11,7 +11,7 @@
11
11
 
12
12
  import fs from "node:fs";
13
13
  import ctp, { type CallbackOptions } from "napi-ctp";
14
- import type { ErrorType, ILifecycleListener } from "./interfaces.js";
14
+ import type { ErrorType, IErrorReceiver } from "./interfaces.js";
15
15
 
16
16
  export class CTPProvider {
17
17
  protected readonly flowPath: string;
@@ -52,7 +52,7 @@ export class CTPProvider {
52
52
  }
53
53
 
54
54
  protected _isErrorResp(
55
- lifecycle: ILifecycleListener,
55
+ errorReceiver: IErrorReceiver,
56
56
  options: CallbackOptions,
57
57
  error: ErrorType,
58
58
  ) {
@@ -60,7 +60,7 @@ export class CTPProvider {
60
60
  return false;
61
61
  }
62
62
 
63
- lifecycle.onError(
63
+ errorReceiver.onError(
64
64
  error,
65
65
  `${options.rspInfo.ErrorID}:${options.rspInfo.ErrorMsg}`,
66
66
  );
package/src/tape.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * tape.ts
3
3
  *
4
- * Copyright (c) 2025 Xiongfei Shi
4
+ * Copyright (c) 2025-2026 Xiongfei Shi
5
5
  *
6
6
  * Author: Xiongfei Shi <xiongfei.shi(a)icloud.com>
7
7
  * License: Apache-2.0
package/src/trader.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * trader.ts
3
3
  *
4
- * Copyright (c) 2025 Xiongfei Shi
4
+ * Copyright (c) 2025-2026 Xiongfei Shi
5
5
  *
6
6
  * Author: Xiongfei Shi <xiongfei.shi(a)icloud.com>
7
7
  * License: Apache-2.0
@@ -57,6 +57,7 @@ import type {
57
57
  import type {
58
58
  ICancelOrderResultReceiver,
59
59
  ICommissionRateReceiver,
60
+ IErrorReceiver,
60
61
  IInstrumentReceiver,
61
62
  IInstrumentsReceiver,
62
63
  ILifecycleListener,
@@ -122,6 +123,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
122
123
  private readonly userInfo: CTPUserInfo;
123
124
  private readonly receivers: IOrderReceiver[];
124
125
  private readonly accounts: TradingAccountField[];
126
+ private readonly investorPositions: InvestorPositionField[];
125
127
  private readonly positionDetails: InvestorPositionDetailField[];
126
128
  private readonly instruments: Map<string, InstrumentField>;
127
129
  private readonly positions: Map<string, PositionInfo>;
@@ -155,6 +157,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
155
157
  this.userInfo = userInfo;
156
158
  this.receivers = [];
157
159
  this.accounts = [];
160
+ this.investorPositions = [];
158
161
  this.positionDetails = [];
159
162
  this.instruments = new Map();
160
163
  this.positions = new Map();
@@ -177,7 +180,39 @@ export class Trader extends CTPProvider implements ITraderProvider {
177
180
  }
178
181
  }
179
182
 
180
- open(lifecycle: ILifecycleListener) {
183
+ getTradingAccountsRaw() {
184
+ return this.accounts;
185
+ }
186
+
187
+ getInvestorPositionsRaw() {
188
+ return this.investorPositions;
189
+ }
190
+
191
+ getInvestorPositionDetailsRaw() {
192
+ return this.positionDetails;
193
+ }
194
+
195
+ getInstrumentsRaw() {
196
+ return Array.from(this.instruments.values());
197
+ }
198
+
199
+ getOrdersRaw() {
200
+ return Array.from(this.orders.values());
201
+ }
202
+
203
+ getTradesRaw() {
204
+ return Array.from(this.trades.values());
205
+ }
206
+
207
+ getInstrumentMarginRatesRaw() {
208
+ return Array.from(this.marginRates.values());
209
+ }
210
+
211
+ getInstrumentCommissionRatesRaw() {
212
+ return Array.from(this.commRates.values());
213
+ }
214
+
215
+ open(lifecycle: ILifecycleListener, errorReceiver: IErrorReceiver) {
181
216
  if (this.traderApi) {
182
217
  return true;
183
218
  }
@@ -197,7 +232,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
197
232
  this.traderApi.on<RspAuthenticateField>(
198
233
  ctp.TraderEvent.RspAuthenticate,
199
234
  (_, options) => {
200
- if (this._isErrorResp(lifecycle, options, "login-error")) {
235
+ if (this._isErrorResp(errorReceiver, options, "login-error")) {
201
236
  return;
202
237
  }
203
238
 
@@ -208,7 +243,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
208
243
  this.traderApi.on<RspUserLoginField>(
209
244
  ctp.TraderEvent.RspUserLogin,
210
245
  (rspUserLogin, options) => {
211
- if (this._isErrorResp(lifecycle, options, "login-error")) {
246
+ if (this._isErrorResp(errorReceiver, options, "login-error")) {
212
247
  return;
213
248
  }
214
249
 
@@ -235,7 +270,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
235
270
  this.traderApi.on<SettlementInfoConfirmField>(
236
271
  ctp.TraderEvent.RspSettlementInfoConfirm,
237
272
  (_, options) => {
238
- if (this._isErrorResp(lifecycle, options, "login-error")) {
273
+ if (this._isErrorResp(errorReceiver, options, "login-error")) {
239
274
  return;
240
275
  }
241
276
 
@@ -247,7 +282,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
247
282
  this.traderApi.on<OrderField>(
248
283
  ctp.TraderEvent.RspQryOrder,
249
284
  (order, options) => {
250
- if (this._isErrorResp(lifecycle, options, "query-order-error")) {
285
+ if (this._isErrorResp(errorReceiver, options, "query-order-error")) {
251
286
  return;
252
287
  }
253
288
 
@@ -266,7 +301,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
266
301
  this.traderApi.on<TradeField>(
267
302
  ctp.TraderEvent.RspQryTrade,
268
303
  (trade, options) => {
269
- if (this._isErrorResp(lifecycle, options, "query-trade-error")) {
304
+ if (this._isErrorResp(errorReceiver, options, "query-trade-error")) {
270
305
  return;
271
306
  }
272
307
 
@@ -291,7 +326,9 @@ export class Trader extends CTPProvider implements ITraderProvider {
291
326
  this.traderApi.on<InstrumentField>(
292
327
  ctp.TraderEvent.RspQryInstrument,
293
328
  (instrument, options) => {
294
- if (this._isErrorResp(lifecycle, options, "query-instrument-error")) {
329
+ if (
330
+ this._isErrorResp(errorReceiver, options, "query-instrument-error")
331
+ ) {
295
332
  return;
296
333
  }
297
334
 
@@ -306,6 +343,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
306
343
 
307
344
  if (options.isLast) {
308
345
  this.positions.clear();
346
+ this.investorPositions.splice(0, this.investorPositions.length);
309
347
 
310
348
  this._withRetry(() =>
311
349
  this.traderApi!.reqQryInvestorPosition(this.userInfo),
@@ -319,7 +357,9 @@ export class Trader extends CTPProvider implements ITraderProvider {
319
357
  this.traderApi.on<InvestorPositionField>(
320
358
  ctp.TraderEvent.RspQryInvestorPosition,
321
359
  (position, options) => {
322
- if (this._isErrorResp(lifecycle, options, "query-positions-error")) {
360
+ if (
361
+ this._isErrorResp(errorReceiver, options, "query-positions-error")
362
+ ) {
323
363
  return;
324
364
  }
325
365
 
@@ -358,6 +398,8 @@ export class Trader extends CTPProvider implements ITraderProvider {
358
398
  break;
359
399
  }
360
400
  }
401
+
402
+ this.investorPositions.push(position);
361
403
  }
362
404
 
363
405
  if (options.isLast) {
@@ -367,12 +409,16 @@ export class Trader extends CTPProvider implements ITraderProvider {
367
409
  }
368
410
 
369
411
  if (this.accountsQueue.size() > 0) {
412
+ this.accounts.splice(0, this.accounts.length);
413
+
370
414
  this._withRetry(() =>
371
415
  this.traderApi!.reqQryTradingAccount(this.userInfo),
372
416
  );
373
417
  }
374
418
 
375
419
  if (this.positionDetailsQueue.size() > 0) {
420
+ this.positionDetails.splice(0, this.positionDetails.length);
421
+
376
422
  this._withRetry(() =>
377
423
  this.traderApi!.reqQryInvestorPositionDetail(this.userInfo),
378
424
  );
@@ -530,7 +576,9 @@ export class Trader extends CTPProvider implements ITraderProvider {
530
576
  (marginRate, options) => {
531
577
  const query = this.marginRatesQueue.shift();
532
578
 
533
- if (this._isErrorResp(lifecycle, options, "query-margin-rate-error")) {
579
+ if (
580
+ this._isErrorResp(errorReceiver, options, "query-margin-rate-error")
581
+ ) {
534
582
  if (query) {
535
583
  query.receiver.onMarginRate(undefined);
536
584
  }
@@ -558,7 +606,11 @@ export class Trader extends CTPProvider implements ITraderProvider {
558
606
  const query = this.commRatesQueue.shift();
559
607
 
560
608
  if (
561
- this._isErrorResp(lifecycle, options, "query-commission-rate-error")
609
+ this._isErrorResp(
610
+ errorReceiver,
611
+ options,
612
+ "query-commission-rate-error",
613
+ )
562
614
  ) {
563
615
  if (query) {
564
616
  query.receiver.onCommissionRate(undefined);
@@ -584,7 +636,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
584
636
  this.traderApi.on<TradingAccountField>(
585
637
  ctp.TraderEvent.RspQryTradingAccount,
586
638
  (account, options) => {
587
- if (this._isErrorResp(lifecycle, options, "query-accounts-error")) {
639
+ if (this._isErrorResp(errorReceiver, options, "query-accounts-error")) {
588
640
  const receivers = this.accountsQueue.toArray();
589
641
 
590
642
  receivers.forEach((receiver) =>
@@ -615,7 +667,11 @@ export class Trader extends CTPProvider implements ITraderProvider {
615
667
  ctp.TraderEvent.RspQryInvestorPositionDetail,
616
668
  (positionDetail, options) => {
617
669
  if (
618
- this._isErrorResp(lifecycle, options, "query-position-details-error")
670
+ this._isErrorResp(
671
+ errorReceiver,
672
+ options,
673
+ "query-position-details-error",
674
+ )
619
675
  ) {
620
676
  const receivers = this.positionDetailsQueue.toArray();
621
677
 
@@ -686,7 +742,11 @@ export class Trader extends CTPProvider implements ITraderProvider {
686
742
  ctp.TraderEvent.RspQryDepthMarketData,
687
743
  (depthMarketData, options) => {
688
744
  if (
689
- this._isErrorResp(lifecycle, options, "query-depth-market-data-error")
745
+ this._isErrorResp(
746
+ errorReceiver,
747
+ options,
748
+ "query-depth-market-data-error",
749
+ )
690
750
  ) {
691
751
  this._clearAllMarketOrders();
692
752
  return;
@@ -754,7 +814,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
754
814
  return true;
755
815
  }
756
816
 
757
- close(lifecycle: ILifecycleListener) {
817
+ close(lifecycle: ILifecycleListener, _errorReceiver: IErrorReceiver) {
758
818
  if (!this.traderApi) {
759
819
  return;
760
820
  }
@@ -959,10 +1019,11 @@ export class Trader extends CTPProvider implements ITraderProvider {
959
1019
  }
960
1020
 
961
1021
  this.positionDetailsQueue.push(receiver);
962
- this.positionDetails.splice(0, this.positionDetails.length);
1022
+ this.investorPositions.splice(0, this.investorPositions.length);
1023
+ this.positions.clear();
963
1024
 
964
1025
  this._withRetry(() =>
965
- this.traderApi?.reqQryInvestorPositionDetail(this.userInfo),
1026
+ this.traderApi!.reqQryInvestorPosition(this.userInfo),
966
1027
  );
967
1028
  }
968
1029
 
package/src/typedef.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * typedef.ts
3
3
  *
4
- * Copyright (c) 2025 Xiongfei Shi
4
+ * Copyright (c) 2025-2026 Xiongfei Shi
5
5
  *
6
6
  * Author: Xiongfei Shi <xiongfei.shi(a)icloud.com>
7
7
  * License: Apache-2.0
package/src/utils.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * utils.ts
3
3
  *
4
- * Copyright (c) 2025 Xiongfei Shi
4
+ * Copyright (c) 2025-2026 Xiongfei Shi
5
5
  *
6
6
  * Author: Xiongfei Shi <xiongfei.shi(a)icloud.com>
7
7
  * License: Apache-2.0