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/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
@@ -10,14 +10,36 @@
10
10
  */
11
11
 
12
12
  import Denque from "denque";
13
- import ctp from "napi-ctp";
13
+ import ctp, { type Trader as TraderApi } from "napi-ctp";
14
+ import type {
15
+ DepthMarketDataField,
16
+ DirectionType,
17
+ InputOrderActionField,
18
+ InputOrderField,
19
+ InstrumentCommissionRateField,
20
+ InstrumentField,
21
+ InstrumentMarginRateField,
22
+ InvestorPositionDetailField,
23
+ InvestorPositionField,
24
+ OffsetFlagType,
25
+ OptionsTypeType,
26
+ OrderField,
27
+ OrderPriceTypeType,
28
+ ProductClassType,
29
+ RspAuthenticateField,
30
+ RspUserLoginField,
31
+ SettlementInfoConfirmField,
32
+ TradeField,
33
+ TradingAccountField,
34
+ } from "@napi-ctp/types";
14
35
  import { CTPProvider } from "./provider.js";
15
36
  import { isValidPrice, parseSymbol } from "./utils.js";
16
- import {
37
+ import type {
17
38
  CommissionRate,
18
39
  InstrumentData,
19
40
  MarginRate,
20
41
  OffsetType,
42
+ OptionsType,
21
43
  OrderData,
22
44
  OrderFlag,
23
45
  OrderStatistic,
@@ -32,9 +54,10 @@ import {
32
54
  TradingAccount,
33
55
  Writeable,
34
56
  } from "./typedef.js";
35
- import {
57
+ import type {
36
58
  ICancelOrderResultReceiver,
37
59
  ICommissionRateReceiver,
60
+ IErrorReceiver,
38
61
  IInstrumentReceiver,
39
62
  IInstrumentsReceiver,
40
63
  ILifecycleListener,
@@ -89,7 +112,7 @@ export type TraderOptions = {
89
112
  };
90
113
 
91
114
  export class Trader extends CTPProvider implements ITraderProvider {
92
- private traderApi?: ctp.Trader;
115
+ private traderApi?: TraderApi;
93
116
  private tradingDay: number;
94
117
  private frontId: number;
95
118
  private sessionId: number;
@@ -99,14 +122,14 @@ export class Trader extends CTPProvider implements ITraderProvider {
99
122
  private readonly fastQueryLastTick?: FastQueryLastTickFunc;
100
123
  private readonly userInfo: CTPUserInfo;
101
124
  private readonly receivers: IOrderReceiver[];
102
- private readonly accounts: ctp.TradingAccountField[];
103
- private readonly positionDetails: ctp.InvestorPositionDetailField[];
104
- private readonly instruments: Map<string, ctp.InstrumentField>;
125
+ private readonly accounts: TradingAccountField[];
126
+ private readonly positionDetails: InvestorPositionDetailField[];
127
+ private readonly instruments: Map<string, InstrumentField>;
105
128
  private readonly positions: Map<string, PositionInfo>;
106
- private readonly orders: Map<string, ctp.OrderField>;
107
- private readonly trades: Map<string, ctp.TradeField[]>;
108
- private readonly marginRates: Map<string, ctp.InstrumentMarginRateField>;
109
- private readonly commRates: Map<string, ctp.InstrumentCommissionRateField>;
129
+ private readonly orders: Map<string, OrderField>;
130
+ private readonly trades: Map<string, TradeField[]>;
131
+ private readonly marginRates: Map<string, InstrumentMarginRateField>;
132
+ private readonly commRates: Map<string, InstrumentCommissionRateField>;
110
133
  private readonly placeOrders: Map<number, IPlaceOrderResultReceiver>;
111
134
  private readonly cancelOrders: Map<number, ICancelOrderResultReceiver>;
112
135
  private readonly marketOrdersQueue: Map<string, Denque<MarketOrder>>;
@@ -155,7 +178,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
155
178
  }
156
179
  }
157
180
 
158
- open(lifecycle: ILifecycleListener) {
181
+ open(lifecycle: ILifecycleListener, errorReceiver: IErrorReceiver) {
159
182
  if (this.traderApi) {
160
183
  return true;
161
184
  }
@@ -172,10 +195,10 @@ export class Trader extends CTPProvider implements ITraderProvider {
172
195
  this.cancelOrders.clear();
173
196
  });
174
197
 
175
- this.traderApi.on<ctp.RspAuthenticateField>(
198
+ this.traderApi.on<RspAuthenticateField>(
176
199
  ctp.TraderEvent.RspAuthenticate,
177
200
  (_, options) => {
178
- if (this._isErrorResp(lifecycle, options, "login-error")) {
201
+ if (this._isErrorResp(errorReceiver, options, "login-error")) {
179
202
  return;
180
203
  }
181
204
 
@@ -183,10 +206,10 @@ export class Trader extends CTPProvider implements ITraderProvider {
183
206
  },
184
207
  );
185
208
 
186
- this.traderApi.on<ctp.RspUserLoginField>(
209
+ this.traderApi.on<RspUserLoginField>(
187
210
  ctp.TraderEvent.RspUserLogin,
188
211
  (rspUserLogin, options) => {
189
- if (this._isErrorResp(lifecycle, options, "login-error")) {
212
+ if (this._isErrorResp(errorReceiver, options, "login-error")) {
190
213
  return;
191
214
  }
192
215
 
@@ -210,10 +233,10 @@ export class Trader extends CTPProvider implements ITraderProvider {
210
233
  },
211
234
  );
212
235
 
213
- this.traderApi.on<ctp.SettlementInfoConfirmField>(
236
+ this.traderApi.on<SettlementInfoConfirmField>(
214
237
  ctp.TraderEvent.RspSettlementInfoConfirm,
215
238
  (_, options) => {
216
- if (this._isErrorResp(lifecycle, options, "login-error")) {
239
+ if (this._isErrorResp(errorReceiver, options, "login-error")) {
217
240
  return;
218
241
  }
219
242
 
@@ -222,10 +245,10 @@ export class Trader extends CTPProvider implements ITraderProvider {
222
245
  },
223
246
  );
224
247
 
225
- this.traderApi.on<ctp.OrderField>(
248
+ this.traderApi.on<OrderField>(
226
249
  ctp.TraderEvent.RspQryOrder,
227
250
  (order, options) => {
228
- if (this._isErrorResp(lifecycle, options, "query-order-error")) {
251
+ if (this._isErrorResp(errorReceiver, options, "query-order-error")) {
229
252
  return;
230
253
  }
231
254
 
@@ -241,10 +264,10 @@ export class Trader extends CTPProvider implements ITraderProvider {
241
264
  },
242
265
  );
243
266
 
244
- this.traderApi.on<ctp.TradeField>(
267
+ this.traderApi.on<TradeField>(
245
268
  ctp.TraderEvent.RspQryTrade,
246
269
  (trade, options) => {
247
- if (this._isErrorResp(lifecycle, options, "query-trade-error")) {
270
+ if (this._isErrorResp(errorReceiver, options, "query-trade-error")) {
248
271
  return;
249
272
  }
250
273
 
@@ -266,10 +289,12 @@ export class Trader extends CTPProvider implements ITraderProvider {
266
289
  },
267
290
  );
268
291
 
269
- this.traderApi.on<ctp.InstrumentField>(
292
+ this.traderApi.on<InstrumentField>(
270
293
  ctp.TraderEvent.RspQryInstrument,
271
294
  (instrument, options) => {
272
- if (this._isErrorResp(lifecycle, options, "query-instrument-error")) {
295
+ if (
296
+ this._isErrorResp(errorReceiver, options, "query-instrument-error")
297
+ ) {
273
298
  return;
274
299
  }
275
300
 
@@ -294,10 +319,12 @@ export class Trader extends CTPProvider implements ITraderProvider {
294
319
 
295
320
  let fired = false;
296
321
 
297
- this.traderApi.on<ctp.InvestorPositionField>(
322
+ this.traderApi.on<InvestorPositionField>(
298
323
  ctp.TraderEvent.RspQryInvestorPosition,
299
324
  (position, options) => {
300
- if (this._isErrorResp(lifecycle, options, "query-positions-error")) {
325
+ if (
326
+ this._isErrorResp(errorReceiver, options, "query-positions-error")
327
+ ) {
301
328
  return;
302
329
  }
303
330
 
@@ -362,7 +389,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
362
389
  },
363
390
  );
364
391
 
365
- this.traderApi.on<ctp.OrderField>(ctp.TraderEvent.RtnOrder, (order) => {
392
+ this.traderApi.on<OrderField>(ctp.TraderEvent.RtnOrder, (order) => {
366
393
  const orderId = this._calcOrderId(order);
367
394
  const current = this.orders.get(orderId);
368
395
 
@@ -469,7 +496,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
469
496
  }
470
497
  });
471
498
 
472
- this.traderApi.on<ctp.TradeField>(ctp.TraderEvent.RtnTrade, (trade) => {
499
+ this.traderApi.on<TradeField>(ctp.TraderEvent.RtnTrade, (trade) => {
473
500
  const orderId = this._calcOrderId(trade);
474
501
  const trades = this.trades.get(orderId);
475
502
 
@@ -503,12 +530,14 @@ export class Trader extends CTPProvider implements ITraderProvider {
503
530
  }
504
531
  });
505
532
 
506
- this.traderApi.on<ctp.InstrumentMarginRateField>(
533
+ this.traderApi.on<InstrumentMarginRateField>(
507
534
  ctp.TraderEvent.RspQryInstrumentMarginRate,
508
535
  (marginRate, options) => {
509
536
  const query = this.marginRatesQueue.shift();
510
537
 
511
- if (this._isErrorResp(lifecycle, options, "query-margin-rate-error")) {
538
+ if (
539
+ this._isErrorResp(errorReceiver, options, "query-margin-rate-error")
540
+ ) {
512
541
  if (query) {
513
542
  query.receiver.onMarginRate(undefined);
514
543
  }
@@ -530,13 +559,17 @@ export class Trader extends CTPProvider implements ITraderProvider {
530
559
  },
531
560
  );
532
561
 
533
- this.traderApi.on<ctp.InstrumentCommissionRateField>(
562
+ this.traderApi.on<InstrumentCommissionRateField>(
534
563
  ctp.TraderEvent.RspQryInstrumentCommissionRate,
535
564
  (commRate, options) => {
536
565
  const query = this.commRatesQueue.shift();
537
566
 
538
567
  if (
539
- this._isErrorResp(lifecycle, options, "query-commission-rate-error")
568
+ this._isErrorResp(
569
+ errorReceiver,
570
+ options,
571
+ "query-commission-rate-error",
572
+ )
540
573
  ) {
541
574
  if (query) {
542
575
  query.receiver.onCommissionRate(undefined);
@@ -559,10 +592,10 @@ export class Trader extends CTPProvider implements ITraderProvider {
559
592
  },
560
593
  );
561
594
 
562
- this.traderApi.on<ctp.TradingAccountField>(
595
+ this.traderApi.on<TradingAccountField>(
563
596
  ctp.TraderEvent.RspQryTradingAccount,
564
597
  (account, options) => {
565
- if (this._isErrorResp(lifecycle, options, "query-accounts-error")) {
598
+ if (this._isErrorResp(errorReceiver, options, "query-accounts-error")) {
566
599
  const receivers = this.accountsQueue.toArray();
567
600
 
568
601
  receivers.forEach((receiver) =>
@@ -589,11 +622,15 @@ export class Trader extends CTPProvider implements ITraderProvider {
589
622
  },
590
623
  );
591
624
 
592
- this.traderApi.on<ctp.InvestorPositionDetailField>(
625
+ this.traderApi.on<InvestorPositionDetailField>(
593
626
  ctp.TraderEvent.RspQryInvestorPositionDetail,
594
627
  (positionDetail, options) => {
595
628
  if (
596
- this._isErrorResp(lifecycle, options, "query-position-details-error")
629
+ this._isErrorResp(
630
+ errorReceiver,
631
+ options,
632
+ "query-position-details-error",
633
+ )
597
634
  ) {
598
635
  const receivers = this.positionDetailsQueue.toArray();
599
636
 
@@ -626,7 +663,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
626
663
  },
627
664
  );
628
665
 
629
- this.traderApi.on<ctp.InputOrderField>(
666
+ this.traderApi.on<InputOrderField>(
630
667
  ctp.TraderEvent.RspOrderInsert,
631
668
  (order, options) => {
632
669
  if (options.rspInfo && order && options.requestId && options.isLast) {
@@ -643,7 +680,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
643
680
  },
644
681
  );
645
682
 
646
- this.traderApi.on<ctp.InputOrderActionField>(
683
+ this.traderApi.on<InputOrderActionField>(
647
684
  ctp.TraderEvent.RspOrderAction,
648
685
  (order, options) => {
649
686
  if (options.rspInfo && order && options.requestId && options.isLast) {
@@ -660,11 +697,15 @@ export class Trader extends CTPProvider implements ITraderProvider {
660
697
  },
661
698
  );
662
699
 
663
- this.traderApi.on<ctp.DepthMarketDataField>(
700
+ this.traderApi.on<DepthMarketDataField>(
664
701
  ctp.TraderEvent.RspQryDepthMarketData,
665
702
  (depthMarketData, options) => {
666
703
  if (
667
- this._isErrorResp(lifecycle, options, "query-depth-market-data-error")
704
+ this._isErrorResp(
705
+ errorReceiver,
706
+ options,
707
+ "query-depth-market-data-error",
708
+ )
668
709
  ) {
669
710
  this._clearAllMarketOrders();
670
711
  return;
@@ -732,7 +773,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
732
773
  return true;
733
774
  }
734
775
 
735
- close(lifecycle: ILifecycleListener) {
776
+ close(lifecycle: ILifecycleListener, _errorReceiver: IErrorReceiver) {
736
777
  if (!this.traderApi) {
737
778
  return;
738
779
  }
@@ -1179,6 +1220,11 @@ export class Trader extends CTPProvider implements ITraderProvider {
1179
1220
  flag: OrderFlag,
1180
1221
  receiver: IPlaceOrderResultReceiver,
1181
1222
  ) {
1223
+ if (volume <= 0) {
1224
+ receiver.onPlaceOrderError("Invalid Volume");
1225
+ return;
1226
+ }
1227
+
1182
1228
  switch (flag) {
1183
1229
  case "limit":
1184
1230
  return this._placeLimitOrder(
@@ -1241,19 +1287,16 @@ export class Trader extends CTPProvider implements ITraderProvider {
1241
1287
  return `${instrument.InstrumentID}.${instrument.ExchangeID}`;
1242
1288
  }
1243
1289
 
1244
- private _calcOrderId(orderOrTrade: ctp.OrderField | ctp.TradeField) {
1290
+ private _calcOrderId(orderOrTrade: OrderField | TradeField) {
1245
1291
  const { ExchangeID, TraderID, OrderLocalID } = orderOrTrade;
1246
1292
  return `${ExchangeID}:${TraderID}:${OrderLocalID}`;
1247
1293
  }
1248
1294
 
1249
- private _calcReceiptId(order: ctp.OrderField | ctp.InputOrderActionField) {
1295
+ private _calcReceiptId(order: OrderField | InputOrderActionField) {
1250
1296
  return `${order.FrontID}:${order.SessionID}:${parseInt(order.OrderRef)}`;
1251
1297
  }
1252
1298
 
1253
- private _calcOrderStatus(
1254
- order: ctp.OrderField,
1255
- traded?: number,
1256
- ): OrderStatus {
1299
+ private _calcOrderStatus(order: OrderField, traded?: number): OrderStatus {
1257
1300
  switch (order.OrderStatus) {
1258
1301
  case ctp.OrderStatusType.Unknown:
1259
1302
  return "submitted";
@@ -1279,7 +1322,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
1279
1322
  }
1280
1323
  }
1281
1324
 
1282
- private _calcOrderFlag(orderPriceType: ctp.OrderPriceTypeType): OrderFlag {
1325
+ private _calcOrderFlag(orderPriceType: OrderPriceTypeType): OrderFlag {
1283
1326
  switch (orderPriceType) {
1284
1327
  case ctp.OrderPriceTypeType.LimitPrice:
1285
1328
  return "limit";
@@ -1289,7 +1332,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
1289
1332
  }
1290
1333
  }
1291
1334
 
1292
- private _calcSideType(direction: ctp.DirectionType): SideType {
1335
+ private _calcSideType(direction: DirectionType): SideType {
1293
1336
  switch (direction) {
1294
1337
  case ctp.DirectionType.Buy:
1295
1338
  return "long";
@@ -1309,7 +1352,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
1309
1352
  }
1310
1353
  }
1311
1354
 
1312
- private _calcOffsetType(offset: ctp.OffsetFlagType): OffsetType {
1355
+ private _calcOffsetType(offset: OffsetFlagType): OffsetType {
1313
1356
  switch (offset) {
1314
1357
  case ctp.OffsetFlagType.Open:
1315
1358
  return "open";
@@ -1335,7 +1378,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
1335
1378
  }
1336
1379
  }
1337
1380
 
1338
- private _calcProductType(productClass: ctp.ProductClassType): ProductType {
1381
+ private _calcProductType(productClass: ProductClassType): ProductType {
1339
1382
  switch (productClass) {
1340
1383
  case ctp.ProductClassType.Futures:
1341
1384
  return "futures";
@@ -1343,11 +1386,32 @@ export class Trader extends CTPProvider implements ITraderProvider {
1343
1386
  case ctp.ProductClassType.Options:
1344
1387
  return "options";
1345
1388
 
1389
+ case ctp.ProductClassType.Spot:
1390
+ return "spot";
1391
+
1392
+ case ctp.ProductClassType.SpotOption:
1393
+ return "spot-options";
1394
+
1346
1395
  default:
1347
1396
  throw new Error(`Unsupported product class: ${productClass}`);
1348
1397
  }
1349
1398
  }
1350
1399
 
1400
+ private _calcOptionsType(
1401
+ optionsType: OptionsTypeType,
1402
+ ): OptionsType | undefined {
1403
+ switch (optionsType) {
1404
+ case ctp.OptionsTypeType.CallOptions:
1405
+ return "call";
1406
+
1407
+ case ctp.OptionsTypeType.PutOptions:
1408
+ return "put";
1409
+
1410
+ default:
1411
+ return undefined;
1412
+ }
1413
+ }
1414
+
1351
1415
  private _ensurePositionInfo(symbol: string): PositionInfo {
1352
1416
  let position = this.positions.get(symbol);
1353
1417
 
@@ -1671,7 +1735,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
1671
1735
  }
1672
1736
  }
1673
1737
 
1674
- private _toTradeData(trade: ctp.TradeField): TradeData {
1738
+ private _toTradeData(trade: TradeField): TradeData {
1675
1739
  return Object.freeze({
1676
1740
  id: trade.TradeID,
1677
1741
  date: parseInt(trade.TradeDate),
@@ -1681,7 +1745,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
1681
1745
  });
1682
1746
  }
1683
1747
 
1684
- private _toOrderData(order: ctp.OrderField): OrderData {
1748
+ private _toOrderData(order: OrderField): OrderData {
1685
1749
  const orderId = this._calcOrderId(order);
1686
1750
  const trades = this.trades.get(orderId) ?? [];
1687
1751
 
@@ -1697,7 +1761,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
1697
1761
  time: this._parseTime(order.InsertTime),
1698
1762
  flag: this._calcOrderFlag(order.OrderPriceType),
1699
1763
  side: this._calcSideType(order.Direction),
1700
- offset: this._calcOffsetType(order.CombOffsetFlag as ctp.OffsetFlagType),
1764
+ offset: this._calcOffsetType(order.CombOffsetFlag as OffsetFlagType),
1701
1765
  price: order.LimitPrice,
1702
1766
  volume: order.VolumeTotalOriginal,
1703
1767
  traded: traded,
@@ -1708,7 +1772,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
1708
1772
  });
1709
1773
  }
1710
1774
 
1711
- private _toInstrumentData(instrument: ctp.InstrumentField): InstrumentData {
1775
+ private _toInstrumentData(instrument: InstrumentField): InstrumentData {
1712
1776
  return Object.freeze({
1713
1777
  symbol: `${instrument.InstrumentID}.${instrument.ExchangeID}`,
1714
1778
  id: instrument.InstrumentID,
@@ -1724,12 +1788,14 @@ export class Trader extends CTPProvider implements ITraderProvider {
1724
1788
  priceTick: instrument.PriceTick,
1725
1789
  maxLimitOrderVolume: instrument.MaxLimitOrderVolume,
1726
1790
  minLimitOrderVolume: instrument.MinLimitOrderVolume,
1791
+ strikePrice: instrument.StrikePrice,
1792
+ optionsType: this._calcOptionsType(instrument.OptionsType),
1727
1793
  });
1728
1794
  }
1729
1795
 
1730
1796
  private _toCommissionRate(
1731
1797
  symbol: string,
1732
- commRate: ctp.InstrumentCommissionRateField,
1798
+ commRate: InstrumentCommissionRateField,
1733
1799
  ): CommissionRate {
1734
1800
  return Object.freeze({
1735
1801
  symbol: symbol,
@@ -1750,7 +1816,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
1750
1816
 
1751
1817
  private _toMarginRate(
1752
1818
  symbol: string,
1753
- marginRate: ctp.InstrumentMarginRateField,
1819
+ marginRate: InstrumentMarginRateField,
1754
1820
  ): MarginRate {
1755
1821
  return Object.freeze({
1756
1822
  symbol: symbol,
@@ -1765,11 +1831,12 @@ export class Trader extends CTPProvider implements ITraderProvider {
1765
1831
  });
1766
1832
  }
1767
1833
 
1768
- private _toTradingAccount(account: ctp.TradingAccountField): TradingAccount {
1834
+ private _toTradingAccount(account: TradingAccountField): TradingAccount {
1769
1835
  return Object.freeze({
1770
1836
  id: account.AccountID,
1771
1837
  currency: account.CurrencyID,
1772
1838
  preBalance: account.PreBalance - account.Withdraw + account.Deposit,
1839
+ preMargin: account.PreMargin,
1773
1840
  balance: account.Balance,
1774
1841
  cash: account.Available,
1775
1842
  margin: account.CurrMargin,
@@ -1781,7 +1848,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
1781
1848
  }
1782
1849
 
1783
1850
  private _toPositionDetail(
1784
- positionDetail: ctp.InvestorPositionDetailField,
1851
+ positionDetail: InvestorPositionDetailField,
1785
1852
  ): PositionDetail {
1786
1853
  return Object.freeze({
1787
1854
  symbol: this._toSymbol(positionDetail.InstrumentID)!,
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
@@ -168,6 +168,7 @@ export type TradingAccount = Readonly<{
168
168
  id: string;
169
169
  currency: string;
170
170
  preBalance: number;
171
+ preMargin: number;
171
172
  balance: number;
172
173
  cash: number;
173
174
  margin: number;
@@ -177,7 +178,8 @@ export type TradingAccount = Readonly<{
177
178
  frozenCommission: number;
178
179
  }>;
179
180
 
180
- export type ProductType = "futures" | "options";
181
+ export type ProductType = "futures" | "options" | "spot" | "spot-options";
182
+ export type OptionsType = "call" | "put";
181
183
 
182
184
  export type InstrumentData = Readonly<{
183
185
  symbol: string;
@@ -194,6 +196,8 @@ export type InstrumentData = Readonly<{
194
196
  priceTick: number;
195
197
  maxLimitOrderVolume: number;
196
198
  minLimitOrderVolume: number;
199
+ strikePrice: number;
200
+ optionsType?: OptionsType;
197
201
  }>;
198
202
 
199
203
  export type PriceVolume = Readonly<{
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
@@ -9,9 +9,9 @@
9
9
  * https://github.com/shixiongfei/hft.js
10
10
  */
11
11
 
12
- import { BarData, OrderFlag } from "./typedef.js";
13
- import { BarInfo } from "./bar.js";
14
- import {
12
+ import type { BarData, OrderFlag } from "./typedef.js";
13
+ import type { BarInfo } from "./bar.js";
14
+ import type {
15
15
  IPlaceOrderResultReceiver,
16
16
  IRuntimeEngine,
17
17
  IStrategy,
@@ -21,7 +21,7 @@ export const isValidPrice = (x: number) => x !== Number.MAX_VALUE && x !== 0;
21
21
  export const isValidVolume = (x: number) => x !== Number.MAX_VALUE && x !== 0;
22
22
 
23
23
  export const parseSymbol = (symbol: string): [string, string] => {
24
- const [instrumentId, exchangeId] = symbol.split(".");
24
+ const [instrumentId = "", exchangeId = ""] = symbol.split(".");
25
25
  return [instrumentId, exchangeId];
26
26
  };
27
27
 
@@ -40,28 +40,28 @@ export const mergeBarData = (bars: BarData[]): BarData => {
40
40
  }
41
41
 
42
42
  if (bars.length === 1) {
43
- return bars[1];
43
+ return bars[1]!;
44
44
  }
45
45
 
46
46
  const bar: BarInfo = {
47
- symbol: bars[0].symbol,
48
- date: bars[0].date,
49
- time: bars[0].time,
50
- openInterest: bars[0].openInterest,
51
- openPrice: bars[0].openPrice,
52
- highPrice: bars[0].highPrice,
53
- lowPrice: bars[0].lowPrice,
54
- closePrice: bars[0].closePrice,
55
- volume: bars[0].volume,
56
- amount: bars[0].volume,
57
- delta: bars[0].delta,
58
- poc: bars[0].poc,
59
- buyVolumes: { ...bars[0].buyVolumes },
60
- sellVolumes: { ...bars[0].sellVolumes },
47
+ symbol: bars[0]!.symbol,
48
+ date: bars[0]!.date,
49
+ time: bars[0]!.time,
50
+ openInterest: bars[0]!.openInterest,
51
+ openPrice: bars[0]!.openPrice,
52
+ highPrice: bars[0]!.highPrice,
53
+ lowPrice: bars[0]!.lowPrice,
54
+ closePrice: bars[0]!.closePrice,
55
+ volume: bars[0]!.volume,
56
+ amount: bars[0]!.volume,
57
+ delta: bars[0]!.delta,
58
+ poc: bars[0]!.poc,
59
+ buyVolumes: { ...bars[0]!.buyVolumes },
60
+ sellVolumes: { ...bars[0]!.sellVolumes },
61
61
  };
62
62
 
63
63
  for (let i = 1; i < bars.length; ++i) {
64
- const nextBar = bars[i];
64
+ const nextBar = bars[i]!;
65
65
 
66
66
  bar.openInterest = nextBar.openInterest;
67
67
  bar.closePrice = nextBar.closePrice;
@@ -73,14 +73,9 @@ export const mergeBarData = (bars: BarData[]): BarData => {
73
73
  bar.amount += nextBar.amount;
74
74
 
75
75
  for (const price in nextBar.buyVolumes) {
76
- const volumeDelta = nextBar.buyVolumes[price];
77
-
78
- if (price in bar.buyVolumes) {
79
- bar.buyVolumes[price] += volumeDelta;
80
- } else {
81
- bar.buyVolumes[price] = volumeDelta;
82
- }
76
+ const volumeDelta = nextBar.buyVolumes[price]!;
83
77
 
78
+ bar.buyVolumes[price] = volumeDelta + (bar.buyVolumes[price] ?? 0);
84
79
  bar.delta += volumeDelta;
85
80
 
86
81
  const priceVP = bar.buyVolumes[price] + (bar.sellVolumes[price] ?? 0);
@@ -92,14 +87,9 @@ export const mergeBarData = (bars: BarData[]): BarData => {
92
87
  }
93
88
 
94
89
  for (const price in nextBar.sellVolumes) {
95
- const volumeDelta = nextBar.sellVolumes[price];
96
-
97
- if (price in bar.sellVolumes) {
98
- bar.sellVolumes[price] += volumeDelta;
99
- } else {
100
- bar.sellVolumes[price] = volumeDelta;
101
- }
90
+ const volumeDelta = nextBar.sellVolumes[price]!;
102
91
 
92
+ bar.sellVolumes[price] = volumeDelta + (bar.sellVolumes[price] ?? 0);
103
93
  bar.delta -= volumeDelta;
104
94
 
105
95
  const priceVP = bar.sellVolumes[price] + (bar.buyVolumes[price] ?? 0);