hft-js 0.2.0 → 0.4.0

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/index.test.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * index.test.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
@@ -9,10 +9,10 @@
9
9
  * https://github.com/shixiongfei/hft.js
10
10
  */
11
11
 
12
+ import process from "node:process";
12
13
  import fs from "node:fs";
13
- import { exit } from "node:process";
14
- import ctp from "napi-ctp";
15
- import * as hft from ".";
14
+ import type { DepthMarketDataField } from "@napi-ctp/types";
15
+ import * as hft from "./index.js";
16
16
 
17
17
  export type Configure = {
18
18
  FlowTdPath: string;
@@ -48,7 +48,7 @@ class Strategy implements hft.IStrategy, hft.ITickReceiver, hft.IBarReceiver {
48
48
  onInstrument: (instrument) => {
49
49
  if (!instrument) {
50
50
  console.error("Symbol", this.symbol, "error");
51
- exit(1);
51
+ process.exit(1);
52
52
  }
53
53
 
54
54
  console.log("Instrument", instrument);
@@ -101,22 +101,22 @@ class Strategy implements hft.IStrategy, hft.ITickReceiver, hft.IBarReceiver {
101
101
  console.log(this.lastBar);
102
102
  }
103
103
 
104
- hft.buyOpen(
105
- this.engine,
106
- this,
107
- this.symbol,
108
- 1,
109
- this.lastTick.orderBook.asks.price[0],
110
- {
111
- onPlaceOrderSent: (receiptId) => {
112
- console.log("Open Place Order Receipt Id", receiptId);
113
- },
104
+ const price = this.lastTick.orderBook.asks.price[0];
114
105
 
115
- onPlaceOrderError: (reason) => {
116
- console.error("Open Place Order Error", reason);
117
- },
106
+ if (!price) {
107
+ console.error("Invalid price");
108
+ return;
109
+ }
110
+
111
+ hft.buyOpen(this.engine, this, this.symbol, 1, price, {
112
+ onPlaceOrderSent: (receiptId) => {
113
+ console.log("Open Place Order Receipt Id", receiptId);
118
114
  },
119
- );
115
+
116
+ onPlaceOrderError: (reason) => {
117
+ console.error("Open Place Order Error", reason);
118
+ },
119
+ });
120
120
  }, 30 * 1000);
121
121
  }
122
122
 
@@ -224,7 +224,15 @@ const enableRecorder = false;
224
224
  if (enableRecorder && recorder) {
225
225
  recorder.setRecorder(
226
226
  {
227
- onMarketData: (marketData: ctp.DepthMarketDataField) => {
227
+ onOpen: () => {
228
+ console.log("Market Recorder is started");
229
+ },
230
+
231
+ onClose: () => {
232
+ console.log("Market Recorder is stopped");
233
+ },
234
+
235
+ onMarketData: (marketData: DepthMarketDataField) => {
228
236
  console.log(marketData.InstrumentID, marketData.LastPrice);
229
237
  },
230
238
  },
@@ -245,5 +253,5 @@ broker.addStrategy(new Strategy(broker));
245
253
 
246
254
  if (!broker.start()) {
247
255
  console.error("Broker start failed");
248
- exit(1);
256
+ process.exit(1);
249
257
  }
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * index.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/interfaces.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * interfaces.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
@@ -9,7 +9,7 @@
9
9
  * https://github.com/shixiongfei/hft.js
10
10
  */
11
11
 
12
- import {
12
+ import type {
13
13
  BarData,
14
14
  CommissionRate,
15
15
  InstrumentData,
@@ -65,7 +65,7 @@ export interface IErrorReceiver {
65
65
  onError: (error: ErrorType, message: string) => void;
66
66
  }
67
67
 
68
- export interface ILifecycleListener extends IErrorReceiver {
68
+ export interface ILifecycleListener {
69
69
  onOpen: () => void;
70
70
  onClose: () => void;
71
71
  }
@@ -144,8 +144,12 @@ export interface IPositionDetailsReceiver {
144
144
  }
145
145
 
146
146
  export interface IProvider {
147
- open: (lifecycle: ILifecycleListener) => boolean;
148
- close: (lifecycle: ILifecycleListener) => void;
147
+ open: (
148
+ lifecycle: ILifecycleListener,
149
+ errorReceiver: IErrorReceiver,
150
+ ) => boolean;
151
+
152
+ close: (lifecycle: ILifecycleListener, errorReceiver: IErrorReceiver) => void;
149
153
  }
150
154
 
151
155
  export interface IOrderEmitter {
@@ -179,7 +183,7 @@ export interface IQueryProvider {
179
183
  queryOrders: (receiver: IOrdersReceiver) => void;
180
184
  }
181
185
 
182
- export interface IMarketRecorderReceiver {
186
+ export interface IMarketRecorderReceiver extends ILifecycleListener {
183
187
  onMarketData: (marketData: any) => void;
184
188
  }
185
189
 
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
@@ -9,12 +9,18 @@
9
9
  * https://github.com/shixiongfei/hft.js
10
10
  */
11
11
 
12
- import ctp from "napi-ctp";
12
+ import ctp, { type MarketData as MarketApi } from "napi-ctp";
13
+ import type {
14
+ DepthMarketDataField,
15
+ RspUserLoginField,
16
+ SpecificInstrumentField,
17
+ } from "@napi-ctp/types";
13
18
  import { CTPProvider } from "./provider.js";
14
- import { InstrumentData, OrderBook, TickData } from "./typedef.js";
19
+ import type { InstrumentData, OrderBook, TickData } from "./typedef.js";
15
20
  import { isValidPrice, isValidVolume, parseSymbol } from "./utils.js";
16
21
  import { calcTapeData } from "./tape.js";
17
- import {
22
+ import type {
23
+ IErrorReceiver,
18
24
  ILifecycleListener,
19
25
  IMarketProvider,
20
26
  IMarketRecorderProvider,
@@ -36,7 +42,7 @@ export class Market
36
42
  extends CTPProvider
37
43
  implements IMarketProvider, IMarketRecorderProvider
38
44
  {
39
- private marketApi?: ctp.MarketData;
45
+ private marketApi?: MarketApi;
40
46
  private recorder?: IMarketRecorderReceiver;
41
47
  private recorderSymbols?: IMarketRecorderSymbols;
42
48
  private tradingDay: number;
@@ -83,7 +89,7 @@ export class Market
83
89
  return this.lastTicks.get(instrumentId);
84
90
  }
85
91
 
86
- open(lifecycle: ILifecycleListener) {
92
+ open(lifecycle: ILifecycleListener, errorReceiver: IErrorReceiver) {
87
93
  if (this.marketApi) {
88
94
  return true;
89
95
  }
@@ -96,10 +102,10 @@ export class Market
96
102
 
97
103
  let fired = false;
98
104
 
99
- this.marketApi.on<ctp.RspUserLoginField>(
105
+ this.marketApi.on<RspUserLoginField>(
100
106
  ctp.MarketDataEvent.RspUserLogin,
101
107
  (_, options) => {
102
- if (this._isErrorResp(lifecycle, options, "login-error")) {
108
+ if (this._isErrorResp(errorReceiver, options, "login-error")) {
103
109
  return;
104
110
  }
105
111
 
@@ -128,7 +134,7 @@ export class Market
128
134
  },
129
135
  );
130
136
 
131
- this.marketApi.on<ctp.SpecificInstrumentField>(
137
+ this.marketApi.on<SpecificInstrumentField>(
132
138
  ctp.MarketDataEvent.RspSubMarketData,
133
139
  (instrument) => {
134
140
  if (!this.listener) {
@@ -141,7 +147,7 @@ export class Market
141
147
  },
142
148
  );
143
149
 
144
- this.marketApi.on<ctp.SpecificInstrumentField>(
150
+ this.marketApi.on<SpecificInstrumentField>(
145
151
  ctp.MarketDataEvent.RspUnSubMarketData,
146
152
  (instrument) => {
147
153
  if (!this.listener) {
@@ -154,7 +160,7 @@ export class Market
154
160
  },
155
161
  );
156
162
 
157
- this.marketApi.on<ctp.DepthMarketDataField>(
163
+ this.marketApi.on<DepthMarketDataField>(
158
164
  ctp.MarketDataEvent.RtnDepthMarketData,
159
165
  (depthMarketData) => {
160
166
  const instrumentId = depthMarketData.InstrumentID;
@@ -281,23 +287,22 @@ export class Market
281
287
  orderBook: Object.freeze(orderBook),
282
288
  });
283
289
 
290
+ const lastTick = this.lastTicks.get(instrumentId);
284
291
  const receivers = this.subscribers.get(instrumentId);
285
292
 
293
+ this.lastTicks.set(instrumentId, tick);
294
+
286
295
  if (receivers && receivers.length > 0) {
287
- const lastTick = this.lastTicks.get(instrumentId);
288
296
  const tape = calcTapeData(tick, lastTick);
289
-
290
297
  receivers.forEach((receiver) => receiver.onTick(tick, tape));
291
298
  }
292
-
293
- this.lastTicks.set(instrumentId, tick);
294
299
  },
295
300
  );
296
301
 
297
302
  return true;
298
303
  }
299
304
 
300
- close(lifecycle: ILifecycleListener) {
305
+ close(lifecycle: ILifecycleListener, _errorReceiver: IErrorReceiver) {
301
306
  if (!this.marketApi) {
302
307
  return;
303
308
  }
@@ -336,6 +341,8 @@ export class Market
336
341
  this.marketApi?.subscribeMarketData(Array.from(instrumentIds)),
337
342
  );
338
343
  }
344
+
345
+ this.recorder?.onOpen();
339
346
  }
340
347
 
341
348
  stopRecorder() {
@@ -359,6 +366,8 @@ export class Market
359
366
  this.marketApi?.unsubscribeMarketData(Array.from(instrumentIds)),
360
367
  );
361
368
  }
369
+
370
+ this.recorder?.onClose();
362
371
  }
363
372
 
364
373
  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
@@ -10,8 +10,8 @@
10
10
  */
11
11
 
12
12
  import fs from "node:fs";
13
- import ctp from "napi-ctp";
14
- import { ErrorType, ILifecycleListener } from "./interfaces.js";
13
+ import ctp, { type CallbackOptions } from "napi-ctp";
14
+ import type { ErrorType, IErrorReceiver } from "./interfaces.js";
15
15
 
16
16
  export class CTPProvider {
17
17
  protected readonly flowPath: string;
@@ -52,15 +52,15 @@ export class CTPProvider {
52
52
  }
53
53
 
54
54
  protected _isErrorResp(
55
- lifecycle: ILifecycleListener,
56
- options: ctp.CallbackOptions,
55
+ errorReceiver: IErrorReceiver,
56
+ options: CallbackOptions,
57
57
  error: ErrorType,
58
58
  ) {
59
59
  if (!options.rspInfo) {
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
  );
@@ -69,7 +69,7 @@ export class CTPProvider {
69
69
  }
70
70
 
71
71
  protected _parseTime(time: string) {
72
- const [hour, minute, second] = time.split(":").map((x) => parseInt(x));
73
- return hour * 10000 + minute * 100 + second;
72
+ const [hh = 0, mm = 0, ss = 0] = time.split(":").map((x) => parseInt(x));
73
+ return hh * 10000 + mm * 100 + ss;
74
74
  }
75
75
  }
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
@@ -9,7 +9,7 @@
9
9
  * https://github.com/shixiongfei/hft.js
10
10
  */
11
11
 
12
- import {
12
+ import type {
13
13
  TapeData,
14
14
  TapeDirection,
15
15
  TapeStatus,