hft-js 0.1.0 → 0.1.2

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/interfaces.ts CHANGED
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  import {
13
+ BarData,
13
14
  CommissionRate,
14
15
  InstrumentData,
15
16
  MarginRate,
@@ -20,6 +21,7 @@ import {
20
21
  PositionDetail,
21
22
  ProductType,
22
23
  SideType,
24
+ TapeData,
23
25
  TickData,
24
26
  TradeData,
25
27
  TradingAccount,
@@ -69,7 +71,6 @@ export interface ILifecycleListener extends IErrorReceiver {
69
71
  export interface IOrderReceiver {
70
72
  onEntrust: (order: OrderData) => void;
71
73
  onTrade: (order: OrderData, trade: TradeData) => void;
72
- onFinish: (order: OrderData) => void;
73
74
  onCancel: (order: OrderData) => void;
74
75
  onReject: (order: OrderData) => void;
75
76
  }
@@ -79,7 +80,7 @@ export interface IOrdersReceiver {
79
80
  }
80
81
 
81
82
  export interface ITickReceiver {
82
- onTick: (tick: TickData) => void;
83
+ onTick: (tick: TickData, tape: TapeData) => void;
83
84
  }
84
85
 
85
86
  export interface ITickSubscriber {
@@ -90,9 +91,21 @@ export interface ITickUnsubscriber {
90
91
  unsubscribe: (symbols: string[], receiver: ITickReceiver) => void;
91
92
  }
92
93
 
94
+ export interface IBarReceiver {
95
+ onBar: (bar: BarData) => void;
96
+ }
97
+
98
+ export interface IBarSubscriber {
99
+ subscribeBar: (symbols: string[], receiver: IBarReceiver) => void;
100
+ }
101
+
102
+ export interface IBarUnsubscriber {
103
+ unsubscribeBar: (symbols: string[], receiver: IBarReceiver) => void;
104
+ }
105
+
93
106
  export interface IStrategy extends IRiskManagerReceiver, IOrderReceiver {
94
- onInit: (subscriber: ITickSubscriber) => void;
95
- onDestroy: (unsubscriber: ITickUnsubscriber) => void;
107
+ onInit: () => void;
108
+ onDestroy: () => void;
96
109
  }
97
110
 
98
111
  export interface ICommissionRateReceiver {
@@ -208,7 +221,9 @@ export interface ITraderProvider extends IProvider, IOrderEmitter, IQueryApi {
208
221
  export interface IRuntimeEngine
209
222
  extends IQueryApi,
210
223
  ITickSubscriber,
211
- ITickUnsubscriber {
224
+ ITickUnsubscriber,
225
+ IBarSubscriber,
226
+ IBarUnsubscriber {
212
227
  addStrategy: (strategy: IStrategy) => void;
213
228
  removeStrategy: (strategy: IStrategy) => void;
214
229
 
@@ -231,4 +246,38 @@ export interface IRuntimeEngine
231
246
  order: OrderData,
232
247
  receiver: ICancelOrderResultReceiver,
233
248
  ) => void;
249
+
250
+ buyOpen: (
251
+ strategy: IStrategy,
252
+ symbol: string,
253
+ volume: number,
254
+ price: number,
255
+ receiver: IPlaceOrderResultReceiver,
256
+ ) => void;
257
+
258
+ buyClose: (
259
+ strategy: IStrategy,
260
+ symbol: string,
261
+ volume: number,
262
+ price: number,
263
+ isToday: boolean,
264
+ receiver: IPlaceOrderResultReceiver,
265
+ ) => void;
266
+
267
+ sellOpen: (
268
+ strategy: IStrategy,
269
+ symbol: string,
270
+ volume: number,
271
+ price: number,
272
+ receiver: IPlaceOrderResultReceiver,
273
+ ) => void;
274
+
275
+ sellClose: (
276
+ strategy: IStrategy,
277
+ symbol: string,
278
+ volume: number,
279
+ price: number,
280
+ isToday: boolean,
281
+ receiver: IPlaceOrderResultReceiver,
282
+ ) => void;
234
283
  }
package/src/market.ts CHANGED
@@ -12,6 +12,8 @@
12
12
  import ctp from "napi-ctp";
13
13
  import { CTPProvider, CTPUserInfo } from "./provider.js";
14
14
  import { InstrumentData, OrderBook, TickData } from "./typedef.js";
15
+ import { calcTapeData } from "./tape.js";
16
+ import { parseSymbol } from "./utils.js";
15
17
  import {
16
18
  ILifecycleListener,
17
19
  IMarketProvider,
@@ -27,8 +29,10 @@ export class Market extends CTPProvider implements IMarketProvider {
27
29
  private marketApi?: ctp.MarketData;
28
30
  private recorder?: IMarketRecorderReceiver;
29
31
  private recorderSymbols?: IMarketRecorderSymbols;
32
+ private tradingDay: number;
30
33
  private readonly recordings: Set<string>;
31
34
  private readonly symbols: Map<string, string>;
35
+ private readonly lastTicks: Map<string, TickData>;
32
36
  private readonly subscribers: Map<string, ITickReceiver[]>;
33
37
 
34
38
  constructor(
@@ -37,8 +41,10 @@ export class Market extends CTPProvider implements IMarketProvider {
37
41
  userInfo: CTPUserInfo,
38
42
  ) {
39
43
  super(flowMdPath, frontMdAddrs, userInfo);
44
+ this.tradingDay = 0;
40
45
  this.recordings = new Set();
41
46
  this.symbols = new Map();
47
+ this.lastTicks = new Map();
42
48
  this.subscribers = new Map();
43
49
  }
44
50
 
@@ -74,6 +80,13 @@ export class Market extends CTPProvider implements IMarketProvider {
74
80
  return;
75
81
  }
76
82
 
83
+ const tradingDay = parseInt(this.marketApi!.getTradingDay());
84
+
85
+ if (this.tradingDay !== tradingDay) {
86
+ this.lastTicks.clear();
87
+ this.tradingDay = tradingDay;
88
+ }
89
+
77
90
  const instrumentIds = new Set([
78
91
  ...Array.from(this.recordings),
79
92
  ...Object.keys(this.subscribers),
@@ -107,12 +120,6 @@ export class Market extends CTPProvider implements IMarketProvider {
107
120
  return;
108
121
  }
109
122
 
110
- const receivers = this.subscribers.get(instrumentId);
111
-
112
- if (!receivers || receivers.length === 0) {
113
- return;
114
- }
115
-
116
123
  const orderBook: OrderBook = {
117
124
  asks: { price: [], volume: [] },
118
125
  bids: { price: [], volume: [] },
@@ -225,7 +232,16 @@ export class Market extends CTPProvider implements IMarketProvider {
225
232
  orderBook: Object.freeze(orderBook),
226
233
  });
227
234
 
228
- receivers.forEach((receiver) => receiver.onTick(tick));
235
+ const receivers = this.subscribers.get(instrumentId);
236
+
237
+ if (receivers && receivers.length > 0) {
238
+ const lastTick = this.lastTicks.get(instrumentId);
239
+ const tape = calcTapeData(tick, lastTick);
240
+
241
+ receivers.forEach((receiver) => receiver.onTick(tick, tape));
242
+ }
243
+
244
+ this.lastTicks.set(instrumentId, tick);
229
245
  },
230
246
  );
231
247
 
@@ -252,7 +268,7 @@ export class Market extends CTPProvider implements IMarketProvider {
252
268
  const instrumentIds = new Set<string>();
253
269
 
254
270
  symbols.forEach((symbol) => {
255
- const [instrumentId] = this._parseSymbol(symbol);
271
+ const [instrumentId] = parseSymbol(symbol);
256
272
 
257
273
  if (this.recordings.has(instrumentId)) {
258
274
  return;
@@ -300,7 +316,7 @@ export class Market extends CTPProvider implements IMarketProvider {
300
316
  const instrumentIds = new Set<string>();
301
317
 
302
318
  symbols.forEach((symbol) => {
303
- const [instrumentId] = this._parseSymbol(symbol);
319
+ const [instrumentId] = parseSymbol(symbol);
304
320
  const receivers = this.subscribers.get(instrumentId);
305
321
 
306
322
  if (receivers) {
@@ -328,7 +344,7 @@ export class Market extends CTPProvider implements IMarketProvider {
328
344
  const instrumentIds = new Set<string>();
329
345
 
330
346
  symbols.forEach((symbol) => {
331
- const [instrumentId] = this._parseSymbol(symbol);
347
+ const [instrumentId] = parseSymbol(symbol);
332
348
  const receivers = this.subscribers.get(instrumentId);
333
349
 
334
350
  if (!receivers) {
package/src/provider.ts CHANGED
@@ -59,11 +59,6 @@ export class CTPProvider {
59
59
  }
60
60
  }
61
61
 
62
- protected _parseSymbol(symbol: string): [string, string] {
63
- const [instrumentId, exchangeId] = symbol.split(".");
64
- return [instrumentId, exchangeId];
65
- }
66
-
67
62
  protected _isErrorResp(
68
63
  lifecycle: ILifecycleListener,
69
64
  options: ctp.CallbackOptions,
package/src/tape.ts CHANGED
@@ -148,22 +148,23 @@ const calcTapeStatus = (
148
148
  };
149
149
 
150
150
  export const calcTapeData = (tick: TickData, preTick?: TickData): TapeData => {
151
- const [volumeDelta, interestDelta] = preTick
152
- ? [tick.volume - preTick.volume, tick.openInterest - preTick.openInterest]
153
- : [tick.volume, tick.openInterest - tick.preOpenInterest];
151
+ const volumeDelta = preTick ? tick.volume - preTick.volume : tick.volume;
152
+ const amountDelta = preTick ? tick.amount - preTick.amount : tick.amount;
153
+
154
+ const interestDelta = preTick
155
+ ? tick.openInterest - preTick.openInterest
156
+ : tick.openInterest - tick.preOpenInterest;
154
157
 
155
158
  const tapeType = calcTapeType(volumeDelta, interestDelta);
156
159
  const tapeDirection = calcTapeDirection(tick, preTick);
157
160
  const tapeStatus = calcTapeStatus(tapeType, tapeDirection);
158
161
 
159
- return {
160
- symbol: tick.symbol,
161
- date: tick.date,
162
- time: tick.time,
163
- volumeDelta: volumeDelta,
164
- interestDelta: interestDelta,
162
+ return Object.freeze({
165
163
  type: tapeType,
166
164
  direction: tapeDirection,
167
165
  status: tapeStatus,
168
- };
166
+ interestDelta: interestDelta,
167
+ volumeDelta: volumeDelta,
168
+ amountDelta: amountDelta,
169
+ });
169
170
  };
package/src/trader.ts CHANGED
@@ -12,6 +12,7 @@
12
12
  import Denque from "denque";
13
13
  import ctp from "napi-ctp";
14
14
  import { CTPProvider, CTPUserInfo } from "./provider.js";
15
+ import { parseSymbol } from "./utils.js";
15
16
  import {
16
17
  CommissionRate,
17
18
  InstrumentData,
@@ -63,10 +64,9 @@ export class Trader extends CTPProvider implements ITraderProvider {
63
64
  private accountsQueryTime: number;
64
65
  private positionDetailsChanged: boolean;
65
66
  private readonly receivers: IOrderReceiver[];
66
- private readonly instruments: ctp.InstrumentField[];
67
67
  private readonly accounts: ctp.TradingAccountField[];
68
68
  private readonly positionDetails: ctp.InvestorPositionDetailField[];
69
- private readonly symbols: Map<string, string>;
69
+ private readonly instruments: Map<string, ctp.InstrumentField>;
70
70
  private readonly positions: Map<string, PositionInfo>;
71
71
  private readonly orders: Map<string, ctp.OrderField>;
72
72
  private readonly trades: Map<string, ctp.TradeField[]>;
@@ -92,10 +92,9 @@ export class Trader extends CTPProvider implements ITraderProvider {
92
92
  this.accountsQueryTime = 0;
93
93
  this.positionDetailsChanged = true;
94
94
  this.receivers = [];
95
- this.instruments = [];
96
95
  this.accounts = [];
97
96
  this.positionDetails = [];
98
- this.symbols = new Map();
97
+ this.instruments = new Map();
99
98
  this.positions = new Map();
100
99
  this.orders = new Map();
101
100
  this.trades = new Map();
@@ -211,8 +210,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
211
210
  }
212
211
 
213
212
  if (options.isLast) {
214
- this.symbols.clear();
215
- this.instruments.splice(0, this.instruments.length);
213
+ this.instruments.clear();
216
214
  this._withRetry(() => this.traderApi!.reqQryInstrument());
217
215
  }
218
216
  },
@@ -230,12 +228,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
230
228
  instrument.ProductClass === ctp.ProductClassType.Futures ||
231
229
  instrument.ProductClass === ctp.ProductClassType.Options
232
230
  ) {
233
- this.symbols.set(
234
- instrument.InstrumentID,
235
- `${instrument.InstrumentID}.${instrument.ExchangeID}`,
236
- );
237
-
238
- this.instruments.push(instrument);
231
+ this.instruments.set(instrument.InstrumentID, instrument);
239
232
  }
240
233
  }
241
234
 
@@ -259,7 +252,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
259
252
  }
260
253
 
261
254
  if (position) {
262
- const symbol = this.symbols.get(position.InstrumentID);
255
+ const symbol = this._toSymbol(position.InstrumentID);
263
256
 
264
257
  if (symbol) {
265
258
  let posInfo = this._ensurePositionInfo(symbol);
@@ -338,7 +331,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
338
331
  case "submitted":
339
332
  {
340
333
  const orderData = this._toOrderData(order);
341
- const symbol = this.symbols.get(order.InstrumentID);
334
+ const symbol = this._toSymbol(order.InstrumentID);
342
335
 
343
336
  if (symbol) {
344
337
  if (orderData.offset === "open") {
@@ -365,7 +358,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
365
358
  case "canceled":
366
359
  {
367
360
  const orderData = this._toOrderData(order);
368
- const symbol = this.symbols.get(order.InstrumentID);
361
+ const symbol = this._toSymbol(order.InstrumentID);
369
362
 
370
363
  if (symbol) {
371
364
  if (orderData.offset === "open") {
@@ -415,24 +408,20 @@ export class Trader extends CTPProvider implements ITraderProvider {
415
408
  if (order) {
416
409
  const orderData = this._toOrderData(order);
417
410
  const tradeData = this._toTradeData(trade);
418
- const symbol = this.symbols.get(order.InstrumentID);
411
+ const symbol = this._toSymbol(order.InstrumentID);
419
412
 
420
413
  if (symbol) {
421
414
  this._calcPosition(
422
415
  symbol,
423
416
  orderData.side,
424
417
  orderData.offset,
425
- orderData.volume,
418
+ tradeData.volume,
426
419
  );
427
420
  }
428
421
 
429
422
  this.receivers.forEach((receiver) =>
430
423
  receiver.onTrade(orderData, tradeData),
431
424
  );
432
-
433
- if (orderData.status === "filled") {
434
- this.receivers.forEach((receiver) => receiver.onFinish(orderData));
435
- }
436
425
  }
437
426
  });
438
427
 
@@ -627,7 +616,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
627
616
  }
628
617
 
629
618
  queryCommissionRate(symbol: string, receiver: ICommissionRateReceiver) {
630
- const [instrumentId] = this._parseSymbol(symbol);
619
+ const [instrumentId] = parseSymbol(symbol);
631
620
  const commRate = this.commRates.get(instrumentId);
632
621
 
633
622
  if (commRate) {
@@ -648,7 +637,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
648
637
  }
649
638
 
650
639
  queryMarginRate(symbol: string, receiver: IMarginRateReceiver) {
651
- const [instrumentId] = this._parseSymbol(symbol);
640
+ const [instrumentId] = parseSymbol(symbol);
652
641
  const marginRate = this.marginRates.get(instrumentId);
653
642
 
654
643
  if (marginRate) {
@@ -670,16 +659,13 @@ export class Trader extends CTPProvider implements ITraderProvider {
670
659
  }
671
660
 
672
661
  queryInstrument(symbol: string, receiver: IInstrumentReceiver) {
673
- const [instrumentId, exchangeId] = this._parseSymbol(symbol);
674
-
675
- const instrument = this.instruments.find(
676
- (instrument) =>
677
- instrument.InstrumentID === instrumentId &&
678
- instrument.ExchangeID === exchangeId,
679
- );
662
+ const [instrumentId, exchangeId] = parseSymbol(symbol);
663
+ const instrument = this.instruments.get(instrumentId);
680
664
 
681
665
  receiver.onInstrument(
682
- instrument ? this._toInstrumentData(instrument) : undefined,
666
+ instrument && instrument.ExchangeID === exchangeId
667
+ ? this._toInstrumentData(instrument)
668
+ : undefined,
683
669
  );
684
670
  }
685
671
 
@@ -691,9 +677,9 @@ export class Trader extends CTPProvider implements ITraderProvider {
691
677
  return;
692
678
  }
693
679
 
694
- const [instrumentId] = this._parseSymbol(symbol);
680
+ const [instrumentId] = parseSymbol(symbol);
695
681
 
696
- if (!this.symbols.has(instrumentId)) {
682
+ if (!this.instruments.has(instrumentId)) {
697
683
  receiver.onPosition(undefined);
698
684
  return;
699
685
  }
@@ -715,10 +701,12 @@ export class Trader extends CTPProvider implements ITraderProvider {
715
701
  }
716
702
 
717
703
  queryInstruments(receiver: IInstrumentsReceiver, type?: ProductType) {
704
+ const instruments = Array.from(this.instruments.values());
705
+
718
706
  switch (type) {
719
707
  case "future":
720
708
  receiver.onInstruments(
721
- this.instruments
709
+ instruments
722
710
  .filter(
723
711
  (instrument) =>
724
712
  instrument.ProductClass === ctp.ProductClassType.Futures,
@@ -729,7 +717,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
729
717
 
730
718
  case "option":
731
719
  receiver.onInstruments(
732
- this.instruments
720
+ instruments
733
721
  .filter(
734
722
  (instrument) =>
735
723
  instrument.ProductClass === ctp.ProductClassType.Options,
@@ -739,9 +727,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
739
727
  break;
740
728
 
741
729
  default:
742
- receiver.onInstruments(
743
- this.instruments.map(this._toInstrumentData, this),
744
- );
730
+ receiver.onInstruments(instruments.map(this._toInstrumentData, this));
745
731
  break;
746
732
  }
747
733
  }
@@ -822,11 +808,8 @@ export class Trader extends CTPProvider implements ITraderProvider {
822
808
  return;
823
809
  }
824
810
 
825
- const [instrumentId] = this._parseSymbol(symbol);
826
-
827
- const instrument = this.instruments.find(
828
- (instrument) => instrument.InstrumentID === instrumentId,
829
- );
811
+ const [instrumentId] = parseSymbol(symbol);
812
+ const instrument = this.instruments.get(instrumentId);
830
813
 
831
814
  if (!instrument) {
832
815
  receiver.onPlaceOrderError("Instrument Not Found");
@@ -918,6 +901,16 @@ export class Trader extends CTPProvider implements ITraderProvider {
918
901
  });
919
902
  }
920
903
 
904
+ private _toSymbol(instrumentId: string) {
905
+ const instrument = this.instruments.get(instrumentId);
906
+
907
+ if (!instrument) {
908
+ return undefined;
909
+ }
910
+
911
+ return `${instrument.InstrumentID}.${instrument.ExchangeID}`;
912
+ }
913
+
921
914
  private _calcOrderId(orderOrTrade: ctp.OrderField | ctp.TradeField) {
922
915
  const { ExchangeID, TraderID, OrderLocalID } = orderOrTrade;
923
916
  return `${ExchangeID}:${TraderID}:${OrderLocalID}`;
@@ -1442,7 +1435,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
1442
1435
  positionDetail: ctp.InvestorPositionDetailField,
1443
1436
  ): PositionDetail {
1444
1437
  return Object.freeze({
1445
- symbol: this.symbols.get(positionDetail.InstrumentID)!,
1438
+ symbol: this._toSymbol(positionDetail.InstrumentID)!,
1446
1439
  date: parseInt(positionDetail.OpenDate),
1447
1440
  side: this._calcSideType(positionDetail.Direction),
1448
1441
  price: positionDetail.OpenPrice,
@@ -1471,7 +1464,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
1471
1464
  while (!this.marginRatesQueue.isEmpty()) {
1472
1465
  const nextQuery = this.marginRatesQueue.peekFront()!;
1473
1466
 
1474
- const [instrumentId] = this._parseSymbol(nextQuery.symbol);
1467
+ const [instrumentId] = parseSymbol(nextQuery.symbol);
1475
1468
  const marginRate = this.marginRates.get(instrumentId);
1476
1469
 
1477
1470
  if (marginRate) {
@@ -1496,7 +1489,7 @@ export class Trader extends CTPProvider implements ITraderProvider {
1496
1489
  while (!this.commRatesQueue.isEmpty()) {
1497
1490
  const nextQuery = this.commRatesQueue.peekFront()!;
1498
1491
 
1499
- const [instrumentId] = this._parseSymbol(nextQuery.symbol);
1492
+ const [instrumentId] = parseSymbol(nextQuery.symbol);
1500
1493
  const commRate = this.commRates.get(instrumentId);
1501
1494
 
1502
1495
  if (commRate) {
package/src/typedef.ts CHANGED
@@ -65,14 +65,12 @@ export type TapeStatus =
65
65
  | "invalid";
66
66
 
67
67
  export type TapeData = Readonly<{
68
- symbol: string;
69
- date: number;
70
- time: number;
71
- volumeDelta: number;
72
- interestDelta: number;
73
68
  type: TapeType;
74
69
  direction: TapeDirection;
75
70
  status: TapeStatus;
71
+ interestDelta: number;
72
+ volumeDelta: number;
73
+ amountDelta: number;
76
74
  }>;
77
75
 
78
76
  export type PositionCell = Readonly<{
@@ -89,7 +87,7 @@ export type PositionData = Readonly<{
89
87
  symbol: string;
90
88
  today: PositionSide;
91
89
  history: PositionSide;
92
- pending: { long: number; short: number };
90
+ pending: Readonly<{ long: number; short: number }>;
93
91
  }>;
94
92
 
95
93
  export type SideType = "long" | "short";
@@ -198,4 +196,25 @@ export type InstrumentData = Readonly<{
198
196
  minLimitOrderVolume: number;
199
197
  }>;
200
198
 
199
+ export type PriceVolume = Readonly<{
200
+ [price: number]: number;
201
+ }>;
202
+
203
+ export type BarData = Readonly<{
204
+ symbol: string;
205
+ date: number;
206
+ time: number;
207
+ openInterest: number;
208
+ openPrice: number;
209
+ highPrice: number;
210
+ lowPrice: number;
211
+ closePrice: number;
212
+ volume: number;
213
+ amount: number;
214
+ delta: number;
215
+ poc: number;
216
+ buyVolumes: PriceVolume;
217
+ sellVolumes: PriceVolume;
218
+ }>;
219
+
201
220
  export type Writeable<T> = { -readonly [P in keyof T]: Writeable<T[P]> };
package/src/utils.ts ADDED
@@ -0,0 +1,110 @@
1
+ /*
2
+ * utils.ts
3
+ *
4
+ * Copyright (c) 2025 Xiongfei Shi
5
+ *
6
+ * Author: Xiongfei Shi <xiongfei.shi(a)icloud.com>
7
+ * License: Apache-2.0
8
+ *
9
+ * https://github.com/shixiongfei/hft.js
10
+ */
11
+
12
+ import { BarData } from "./typedef.js";
13
+ import { BarInfo } from "./bar.js";
14
+
15
+ export const parseSymbol = (symbol: string): [string, string] => {
16
+ const [instrumentId, exchangeId] = symbol.split(".");
17
+ return [instrumentId, exchangeId];
18
+ };
19
+
20
+ export const getBarBuyVolume = (bar: BarData, price: number) =>
21
+ bar.buyVolumes[price] ?? 0;
22
+
23
+ export const getBarSellVolume = (bar: BarData, price: number) =>
24
+ bar.sellVolumes[price] ?? 0;
25
+
26
+ export const getBarVolume = (bar: BarData, price: number) =>
27
+ getBarBuyVolume(bar, price) + getBarSellVolume(bar, price);
28
+
29
+ export const mergeBarData = (bars: BarData[]): BarData => {
30
+ if (bars.length === 0) {
31
+ throw new Error("Bars is empty");
32
+ }
33
+
34
+ if (bars.length === 1) {
35
+ return bars[1];
36
+ }
37
+
38
+ const bar: BarInfo = {
39
+ symbol: bars[0].symbol,
40
+ date: bars[0].date,
41
+ time: bars[0].time,
42
+ openInterest: bars[0].openInterest,
43
+ openPrice: bars[0].openPrice,
44
+ highPrice: bars[0].highPrice,
45
+ lowPrice: bars[0].lowPrice,
46
+ closePrice: bars[0].closePrice,
47
+ volume: bars[0].volume,
48
+ amount: bars[0].volume,
49
+ delta: bars[0].delta,
50
+ poc: bars[0].poc,
51
+ buyVolumes: { ...bars[0].buyVolumes },
52
+ sellVolumes: { ...bars[0].sellVolumes },
53
+ };
54
+
55
+ for (let i = 1; i < bars.length; ++i) {
56
+ const nextBar = bars[i];
57
+
58
+ bar.openInterest = nextBar.openInterest;
59
+ bar.closePrice = nextBar.closePrice;
60
+
61
+ bar.highPrice = Math.max(bar.highPrice, nextBar.highPrice);
62
+ bar.lowPrice = Math.min(bar.lowPrice, nextBar.lowPrice);
63
+
64
+ bar.volume += nextBar.volume;
65
+ bar.amount += nextBar.amount;
66
+
67
+ for (const price in nextBar.buyVolumes) {
68
+ const volumeDelta = nextBar.buyVolumes[price];
69
+
70
+ if (price in bar.buyVolumes) {
71
+ bar.buyVolumes[price] += volumeDelta;
72
+ } else {
73
+ bar.buyVolumes[price] = volumeDelta;
74
+ }
75
+
76
+ bar.delta += volumeDelta;
77
+
78
+ const priceVP = bar.buyVolumes[price] + (bar.sellVolumes[price] ?? 0);
79
+ const pocVP = getBarVolume(bar, bar.poc);
80
+
81
+ if (priceVP > pocVP) {
82
+ bar.poc = parseFloat(price);
83
+ }
84
+ }
85
+
86
+ for (const price in nextBar.sellVolumes) {
87
+ const volumeDelta = nextBar.sellVolumes[price];
88
+
89
+ if (price in bar.sellVolumes) {
90
+ bar.sellVolumes[price] += volumeDelta;
91
+ } else {
92
+ bar.sellVolumes[price] = volumeDelta;
93
+ }
94
+
95
+ bar.delta -= volumeDelta;
96
+
97
+ const priceVP = bar.sellVolumes[price] + (bar.buyVolumes[price] ?? 0);
98
+ const pocVP = getBarVolume(bar, bar.poc);
99
+
100
+ if (priceVP > pocVP) {
101
+ bar.poc = parseFloat(price);
102
+ }
103
+ }
104
+ }
105
+
106
+ Object.freeze(bar.buyVolumes);
107
+ Object.freeze(bar.sellVolumes);
108
+
109
+ return Object.freeze(bar);
110
+ };