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/lib/bar.d.ts +15 -0
- package/lib/bar.js +114 -0
- package/lib/bar.js.map +1 -0
- package/lib/broker.d.ts +8 -1
- package/lib/broker.js +42 -2
- package/lib/broker.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/index.test.js +41 -31
- package/lib/index.test.js.map +1 -1
- package/lib/interfaces.d.ts +18 -6
- package/lib/market.d.ts +2 -0
- package/lib/market.js +19 -8
- package/lib/market.js.map +1 -1
- package/lib/provider.d.ts +0 -1
- package/lib/provider.js +0 -4
- package/lib/provider.js.map +1 -1
- package/lib/tape.js +10 -10
- package/lib/tape.js.map +1 -1
- package/lib/trader.d.ts +2 -2
- package/lib/trader.js +34 -32
- package/lib/trader.js.map +1 -1
- package/lib/typedef.d.ts +24 -7
- package/lib/utils.d.ts +6 -0
- package/lib/utils.js +106 -0
- package/lib/utils.js.map +1 -0
- package/package.json +1 -1
- package/src/bar.ts +133 -0
- package/src/broker.ts +120 -2
- package/src/index.test.ts +57 -49
- package/src/index.ts +1 -1
- package/src/interfaces.ts +54 -5
- package/src/market.ts +26 -10
- package/src/provider.ts +0 -5
- package/src/tape.ts +11 -10
- package/src/trader.ts +39 -46
- package/src/typedef.ts +25 -6
- package/src/utils.ts +110 -0
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: (
|
|
95
|
-
onDestroy: (
|
|
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
|
|
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] =
|
|
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] =
|
|
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] =
|
|
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
|
|
152
|
-
|
|
153
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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] =
|
|
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] =
|
|
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] =
|
|
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
|
|
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] =
|
|
680
|
+
const [instrumentId] = parseSymbol(symbol);
|
|
695
681
|
|
|
696
|
-
if (!this.
|
|
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
|
-
|
|
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
|
-
|
|
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] =
|
|
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.
|
|
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] =
|
|
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] =
|
|
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
|
+
};
|