hft-js 0.0.0 → 0.1.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.
@@ -0,0 +1,240 @@
1
+ /*
2
+ * index.test.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 fs from "node:fs";
13
+ import { exit } from "node:process";
14
+ import ctp from "napi-ctp";
15
+ import * as hft from ".";
16
+
17
+ export type Configure = {
18
+ FlowTdPath: string;
19
+ FlowMdPath: string;
20
+ FrontTdAddrs: string[];
21
+ FrontMdAddrs: string[];
22
+ UserInfo: hft.CTPUserInfo;
23
+ };
24
+
25
+ const existsFile = (filename: string) => {
26
+ try {
27
+ fs.accessSync(filename);
28
+ return true;
29
+ } catch {
30
+ return false;
31
+ }
32
+ };
33
+
34
+ const config = JSON.parse(
35
+ fs.readFileSync("test.conf.json", "utf8"),
36
+ ) as Configure;
37
+
38
+ if (!existsFile(config.FlowTdPath)) {
39
+ fs.mkdirSync(config.FlowTdPath, { recursive: true });
40
+ }
41
+
42
+ if (!existsFile(config.FlowMdPath)) {
43
+ fs.mkdirSync(config.FlowMdPath, { recursive: true });
44
+ }
45
+
46
+ class Strategy implements hft.IStrategy, hft.ITickReceiver {
47
+ private lastTick?: hft.TickData;
48
+ private engine: hft.IRuntimeEngine;
49
+ readonly symbol = "ni2505.SHFE";
50
+
51
+ constructor(engine: hft.IRuntimeEngine) {
52
+ this.engine = engine;
53
+ }
54
+
55
+ onInit(subscriber: hft.ITickSubscriber) {
56
+ subscriber.subscribe([this.symbol], this);
57
+ console.log("Strategy init");
58
+
59
+ console.log("Trading Day", this.engine.getTradingDay());
60
+
61
+ this.engine.queryInstrument(this.symbol, {
62
+ onInstrument: (instrument) => {
63
+ if (!instrument) {
64
+ console.error("Symbol", this.symbol, "error");
65
+ exit(1);
66
+ }
67
+
68
+ console.log("Instrument", instrument);
69
+ },
70
+ });
71
+
72
+ this.engine.queryCommissionRate(this.symbol, {
73
+ onCommissionRate: (rate) => {
74
+ console.log("Commission Rate", rate);
75
+ },
76
+ });
77
+
78
+ this.engine.queryMarginRate(this.symbol, {
79
+ onMarginRate: (rate) => {
80
+ console.log("Margin Rate", rate);
81
+ },
82
+ });
83
+
84
+ this.engine.queryTradingAccounts({
85
+ onTradingAccounts: (accounts) => {
86
+ console.log("Trading Accounts", accounts);
87
+ },
88
+ });
89
+
90
+ this.engine.queryOrders({
91
+ onOrders: (orders) => {
92
+ console.log("Orders", orders);
93
+ },
94
+ });
95
+
96
+ this.engine.queryPositionDetails({
97
+ onPositionDetails: (positionDetails) => {
98
+ console.log("Position Details", positionDetails);
99
+ },
100
+ });
101
+
102
+ this.engine.queryPositions({
103
+ onPositions: (positions) => {
104
+ console.log("Positions", positions);
105
+ },
106
+ });
107
+
108
+ setTimeout(() => {
109
+ if (!this.lastTick) {
110
+ console.error("Market data is not found");
111
+ return;
112
+ }
113
+
114
+ this.engine.buyOpen(
115
+ this,
116
+ this.symbol,
117
+ 1,
118
+ this.lastTick.orderBook.asks.price[0],
119
+ {
120
+ onPlaceOrderSent: (receiptId) => {
121
+ console.log("Open Place Order Receipt Id", receiptId);
122
+ },
123
+
124
+ onPlaceOrderError: (reason) => {
125
+ console.error("Open Place Order Error", reason);
126
+ },
127
+ },
128
+ );
129
+ }, 30 * 1000);
130
+ }
131
+
132
+ onDestroy(unsubscriber: hft.ITickUnsubscriber) {
133
+ unsubscriber.unsubscribe([this.symbol], this);
134
+ console.log("Strategy destroy");
135
+ }
136
+
137
+ onRisk(type: hft.RiskType, reason?: string) {
138
+ console.log("Trigger Risk Control", type, reason);
139
+ }
140
+
141
+ onEntrust(order: hft.OrderData) {
142
+ console.log("Entrust order", order);
143
+ }
144
+
145
+ onTrade(order: hft.OrderData, trade: hft.TradeData) {
146
+ console.log("Order", order, "Traded", trade);
147
+
148
+ if (order.status === "filled") {
149
+ setTimeout(() => {
150
+ this.engine.queryPosition(this.symbol, {
151
+ onPosition: (position) => {
152
+ if (!position || !this.lastTick) {
153
+ return;
154
+ }
155
+
156
+ const todayLong =
157
+ position.today.long.position - position.today.long.frozen;
158
+
159
+ if (todayLong > 0) {
160
+ this.engine.sellClose(
161
+ this,
162
+ this.symbol,
163
+ todayLong,
164
+ this.lastTick.orderBook.bids.price[0],
165
+ true,
166
+ {
167
+ onPlaceOrderSent: (receiptId) => {
168
+ console.log("Close Place Order Receipt Id", receiptId);
169
+ },
170
+
171
+ onPlaceOrderError: (reason) => {
172
+ console.error("Close Place Order Error", reason);
173
+ },
174
+ },
175
+ );
176
+ }
177
+ },
178
+ });
179
+ }, 30 * 1000);
180
+ }
181
+ }
182
+
183
+ onCancel(order: hft.OrderData) {
184
+ console.log("Cancel Order", order);
185
+ }
186
+
187
+ onReject(order: hft.OrderData) {
188
+ console.log("Reject Order", order);
189
+ }
190
+
191
+ onTick(tick: hft.TickData) {
192
+ //const tape = hft.calcTapeData(tick, this.lastTick);
193
+
194
+ //console.log(tick);
195
+ //console.log(tape);
196
+
197
+ this.lastTick = tick;
198
+ }
199
+ }
200
+
201
+ const trader = hft.createTrader(
202
+ config.FlowTdPath,
203
+ config.FrontTdAddrs,
204
+ config.UserInfo,
205
+ );
206
+
207
+ const market = hft.createMarket(
208
+ config.FlowMdPath,
209
+ config.FrontMdAddrs,
210
+ config.UserInfo,
211
+ );
212
+
213
+ const enableRecorder = false;
214
+
215
+ if (enableRecorder) {
216
+ market.setRecorder(
217
+ {
218
+ onMarketData: (marketData: ctp.DepthMarketDataField) => {
219
+ console.log(marketData.InstrumentID, marketData.LastPrice);
220
+ },
221
+ },
222
+ (instruments) =>
223
+ instruments
224
+ .filter((instrument) => instrument.productType === "future")
225
+ .map((instrument) => instrument.symbol),
226
+ );
227
+ }
228
+
229
+ const broker = hft.createBroker(trader, market, {
230
+ onError(error, message) {
231
+ console.error(error, message);
232
+ },
233
+ });
234
+
235
+ broker.addStrategy(new Strategy(broker));
236
+
237
+ if (!broker.start()) {
238
+ console.error("Broker start failed");
239
+ exit(1);
240
+ }
package/src/index.ts CHANGED
@@ -1 +1,18 @@
1
- console.log("High-Frequency Trading in Node.js");
1
+ /*
2
+ * index.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
+ export * from "./typedef.js";
13
+ export * from "./interfaces.js";
14
+ export * from "./broker.js";
15
+ export * from "./tape.js";
16
+ export * from "./trader.js";
17
+ export * from "./market.js";
18
+ export { CTPUserInfo } from "./provider.js";
@@ -0,0 +1,267 @@
1
+ /*
2
+ * interfaces.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 {
13
+ CommissionRate,
14
+ InstrumentData,
15
+ MarginRate,
16
+ OffsetType,
17
+ OrderData,
18
+ OrderFlag,
19
+ PositionData,
20
+ PositionDetail,
21
+ ProductType,
22
+ SideType,
23
+ TickData,
24
+ TradeData,
25
+ TradingAccount,
26
+ } from "./typedef.js";
27
+
28
+ export type RiskType = "place-order-risk" | "cancel-order-risk";
29
+
30
+ export interface IPlaceOrderRiskManager {
31
+ onPlaceOrder: (
32
+ symbol: string,
33
+ offset: OffsetType,
34
+ side: SideType,
35
+ volume: number,
36
+ price: number,
37
+ flag: OrderFlag,
38
+ ) => boolean | string;
39
+ }
40
+
41
+ export interface ICancelOrderRiskManager {
42
+ onCancelOrder: (order: OrderData) => boolean | string;
43
+ }
44
+
45
+ export interface IRiskManagerReceiver {
46
+ onRisk: (type: RiskType, reason?: string) => void;
47
+ }
48
+
49
+ export type ErrorType =
50
+ | "login-error"
51
+ | "query-order-error"
52
+ | "query-trade-error"
53
+ | "query-instrument-error"
54
+ | "query-margin-rate-error"
55
+ | "query-commission-rate-error"
56
+ | "query-accounts-error"
57
+ | "query-positions-error"
58
+ | "query-position-details-error";
59
+
60
+ export interface IErrorReceiver {
61
+ onError: (error: ErrorType, message: string) => void;
62
+ }
63
+
64
+ export interface ILifecycleListener extends IErrorReceiver {
65
+ onOpen: () => void;
66
+ onClose: () => void;
67
+ }
68
+
69
+ export interface IOrderReceiver {
70
+ onEntrust: (order: OrderData) => void;
71
+ onTrade: (order: OrderData, trade: TradeData) => void;
72
+ onCancel: (order: OrderData) => void;
73
+ onReject: (order: OrderData) => void;
74
+ }
75
+
76
+ export interface IOrdersReceiver {
77
+ onOrders: (orders: OrderData[]) => void;
78
+ }
79
+
80
+ export interface ITickReceiver {
81
+ onTick: (tick: TickData) => void;
82
+ }
83
+
84
+ export interface ITickSubscriber {
85
+ subscribe: (symbols: string[], receiver: ITickReceiver) => void;
86
+ }
87
+
88
+ export interface ITickUnsubscriber {
89
+ unsubscribe: (symbols: string[], receiver: ITickReceiver) => void;
90
+ }
91
+
92
+ export interface IStrategy extends IRiskManagerReceiver, IOrderReceiver {
93
+ onInit: (subscriber: ITickSubscriber) => void;
94
+ onDestroy: (unsubscriber: ITickUnsubscriber) => void;
95
+ }
96
+
97
+ export interface ICommissionRateReceiver {
98
+ onCommissionRate: (rate: CommissionRate | undefined) => void;
99
+ }
100
+
101
+ export interface IMarginRateReceiver {
102
+ onMarginRate: (rate: MarginRate | undefined) => void;
103
+ }
104
+
105
+ export interface IInstrumentReceiver {
106
+ onInstrument: (instrument: InstrumentData | undefined) => void;
107
+ }
108
+
109
+ export interface IInstrumentsReceiver {
110
+ onInstruments: (instruments: InstrumentData[] | undefined) => void;
111
+ }
112
+
113
+ export interface ITradingAccountsReceiver {
114
+ onTradingAccounts: (accounts: TradingAccount[] | undefined) => void;
115
+ }
116
+
117
+ export interface IPositionReceiver {
118
+ onPosition: (position: PositionData | undefined) => void;
119
+ }
120
+
121
+ export interface IPositionsReceiver {
122
+ onPositions: (positions: PositionData[] | undefined) => void;
123
+ }
124
+
125
+ export interface IPositionDetailsReceiver {
126
+ onPositionDetails: (positionDetails: PositionDetail[] | undefined) => void;
127
+ }
128
+
129
+ export interface IProvider {
130
+ open: (lifecycle: ILifecycleListener) => boolean;
131
+ close: (lifecycle: ILifecycleListener) => void;
132
+ }
133
+
134
+ export interface IOrderEmitter {
135
+ addReceiver: (receiver: IOrderReceiver) => void;
136
+ removeReceiver: (receiver: IOrderReceiver) => void;
137
+ }
138
+
139
+ export interface IQueryApi {
140
+ getTradingDay: () => number;
141
+
142
+ queryCommissionRate: (
143
+ symbol: string,
144
+ receiver: ICommissionRateReceiver,
145
+ ) => void;
146
+
147
+ queryMarginRate: (symbol: string, receiver: IMarginRateReceiver) => void;
148
+ queryInstrument: (symbol: string, receiver: IInstrumentReceiver) => void;
149
+ queryPosition: (symbol: string, receiver: IPositionReceiver) => void;
150
+
151
+ queryInstruments: (
152
+ receiver: IInstrumentsReceiver,
153
+ type?: ProductType,
154
+ ) => void;
155
+
156
+ queryTradingAccounts: (receiver: ITradingAccountsReceiver) => void;
157
+ queryPositions: (receiver: IPositionsReceiver) => void;
158
+ queryPositionDetails: (receiver: IPositionDetailsReceiver) => void;
159
+ queryOrders: (receiver: IOrdersReceiver) => void;
160
+ }
161
+
162
+ export interface IMarketRecorderReceiver {
163
+ onMarketData: (marketData: any) => void;
164
+ }
165
+
166
+ export type IMarketRecorderSymbols = (instrument: InstrumentData[]) => string[];
167
+
168
+ export interface IMarketProvider
169
+ extends IProvider,
170
+ ITickSubscriber,
171
+ ITickUnsubscriber {
172
+ hasRecorder: () => boolean;
173
+
174
+ setRecorder: (
175
+ receiver: IMarketRecorderReceiver,
176
+ symbols: IMarketRecorderSymbols,
177
+ ) => void;
178
+
179
+ startRecorder: (instrument: InstrumentData[]) => void;
180
+ stopRecorder: () => void;
181
+ }
182
+
183
+ export type IPlaceOrderResultReceiver = {
184
+ onPlaceOrderSent: (receiptId: string) => void;
185
+ onPlaceOrderError: (reason: string) => void;
186
+ };
187
+
188
+ export type ICancelOrderResultReceiver = {
189
+ onCancelOrderSent: () => void;
190
+ onCancelOrderError: (reason: string) => void;
191
+ };
192
+
193
+ export interface ITraderProvider extends IProvider, IOrderEmitter, IQueryApi {
194
+ placeOrder: (
195
+ symbol: string,
196
+ offset: OffsetType,
197
+ side: SideType,
198
+ volume: number,
199
+ price: number,
200
+ flag: OrderFlag,
201
+ receiver: IPlaceOrderResultReceiver,
202
+ ) => void;
203
+
204
+ cancelOrder: (order: OrderData, receiver: ICancelOrderResultReceiver) => void;
205
+ }
206
+
207
+ export interface IRuntimeEngine
208
+ extends IQueryApi,
209
+ ITickSubscriber,
210
+ ITickUnsubscriber {
211
+ addStrategy: (strategy: IStrategy) => void;
212
+ removeStrategy: (strategy: IStrategy) => void;
213
+
214
+ addPlaceOrderRiskManager: (riskMgr: IPlaceOrderRiskManager) => void;
215
+ addCancelOrderRiskManager: (riskMgr: ICancelOrderRiskManager) => void;
216
+
217
+ placeOrder: (
218
+ strategy: IStrategy,
219
+ symbol: string,
220
+ offset: OffsetType,
221
+ side: SideType,
222
+ volume: number,
223
+ price: number,
224
+ flag: OrderFlag,
225
+ receiver: IPlaceOrderResultReceiver,
226
+ ) => void;
227
+
228
+ cancelOrder: (
229
+ strategy: IStrategy,
230
+ order: OrderData,
231
+ receiver: ICancelOrderResultReceiver,
232
+ ) => void;
233
+
234
+ buyOpen: (
235
+ strategy: IStrategy,
236
+ symbol: string,
237
+ volume: number,
238
+ price: number,
239
+ receiver: IPlaceOrderResultReceiver,
240
+ ) => void;
241
+
242
+ buyClose: (
243
+ strategy: IStrategy,
244
+ symbol: string,
245
+ volume: number,
246
+ price: number,
247
+ isToday: boolean,
248
+ receiver: IPlaceOrderResultReceiver,
249
+ ) => void;
250
+
251
+ sellOpen: (
252
+ strategy: IStrategy,
253
+ symbol: string,
254
+ volume: number,
255
+ price: number,
256
+ receiver: IPlaceOrderResultReceiver,
257
+ ) => void;
258
+
259
+ sellClose: (
260
+ strategy: IStrategy,
261
+ symbol: string,
262
+ volume: number,
263
+ price: number,
264
+ isToday: boolean,
265
+ receiver: IPlaceOrderResultReceiver,
266
+ ) => void;
267
+ }