hft-js
Version:
High-Frequency Trading in Node.js
252 lines (207 loc) • 5.83 kB
text/typescript
/*
* index.test.ts
*
* Copyright (c) 2025 Xiongfei Shi
*
* Author: Xiongfei Shi <xiongfei.shi(a)icloud.com>
* License: Apache-2.0
*
* https://github.com/shixiongfei/hft.js
*/
import fs from "node:fs";
import { exit } from "node:process";
import ctp from "napi-ctp";
import * as hft from ".";
export type Configure = {
FlowTdPath: string;
FlowMdPath: string;
FrontTdAddrs: string[];
FrontMdAddrs: string[];
UserInfo: hft.CTPUserInfo;
};
const existsFile = (filename: string) => {
try {
fs.accessSync(filename);
return true;
} catch {
return false;
}
};
const config = JSON.parse(
fs.readFileSync("test.conf.json", "utf8"),
) as Configure;
if (!existsFile(config.FlowTdPath)) {
fs.mkdirSync(config.FlowTdPath, { recursive: true });
}
if (!existsFile(config.FlowMdPath)) {
fs.mkdirSync(config.FlowMdPath, { recursive: true });
}
class Strategy implements hft.IStrategy, hft.ITickReceiver, hft.IBarReceiver {
private lastTick?: hft.TickData;
private lastBar?: hft.BarData;
private engine: hft.IRuntimeEngine;
readonly symbol = "ni2505.SHFE";
constructor(engine: hft.IRuntimeEngine) {
this.engine = engine;
}
onInit() {
this.engine.subscribe([this.symbol], this);
this.engine.subscribeBar([this.symbol], this);
console.log("Strategy init");
console.log("Trading Day", this.engine.getTradingDay());
this.engine.queryInstrument(this.symbol, {
onInstrument: (instrument) => {
if (!instrument) {
console.error("Symbol", this.symbol, "error");
exit(1);
}
console.log("Instrument", instrument);
},
});
this.engine.queryCommissionRate(this.symbol, {
onCommissionRate: (rate) => {
console.log("Commission Rate", rate);
},
});
this.engine.queryMarginRate(this.symbol, {
onMarginRate: (rate) => {
console.log("Margin Rate", rate);
},
});
this.engine.queryTradingAccounts({
onTradingAccounts: (accounts) => {
console.log("Trading Accounts", accounts);
},
});
this.engine.queryOrders({
onOrders: (orders) => {
console.log("Orders", orders);
},
});
this.engine.queryPositionDetails({
onPositionDetails: (positionDetails) => {
console.log("Position Details", positionDetails);
},
});
this.engine.queryPositions({
onPositions: (positions) => {
console.log("Positions", positions);
},
});
setTimeout(() => {
if (!this.lastTick) {
console.error("Market data is not found");
return;
}
if (this.lastBar) {
console.log(this.lastBar);
}
this.engine.buyOpen(
this,
this.symbol,
1,
this.lastTick.orderBook.asks.price[0],
{
onPlaceOrderSent: (receiptId) => {
console.log("Open Place Order Receipt Id", receiptId);
},
onPlaceOrderError: (reason) => {
console.error("Open Place Order Error", reason);
},
},
);
}, 30 * 1000);
}
onDestroy() {
this.engine.unsubscribeBar([this.symbol], this);
this.engine.unsubscribe([this.symbol], this);
console.log("Strategy destroy");
}
onRisk(type: hft.RiskType, reason?: string) {
console.log("Trigger Risk Control", type, reason);
}
onEntrust(order: hft.OrderData) {
console.log("Entrust order", order);
}
onTrade(order: hft.OrderData, trade: hft.TradeData) {
console.log("Order", order, "Traded", trade);
if (order.status === "filled") {
setTimeout(() => {
this.engine.queryPosition(this.symbol, {
onPosition: (position) => {
if (!position || !this.lastTick) {
return;
}
const todayLong =
position.today.long.position - position.today.long.frozen;
if (todayLong > 0) {
if (this.lastBar) {
console.log(this.lastBar);
}
this.engine.sellClose(
this,
this.symbol,
todayLong,
this.lastTick.orderBook.bids.price[0],
true,
{
onPlaceOrderSent: (receiptId) => {
console.log("Close Place Order Receipt Id", receiptId);
},
onPlaceOrderError: (reason) => {
console.error("Close Place Order Error", reason);
},
},
);
}
},
});
}, 30 * 1000);
}
}
onCancel(order: hft.OrderData) {
console.log("Cancel Order", order);
}
onReject(order: hft.OrderData) {
console.log("Reject Order", order);
}
onTick(tick: hft.TickData, tape: hft.TapeData) {
//console.log(tick);
//console.log(tape);
this.lastTick = tick;
}
onBar(bar: hft.BarData) {
console.log(bar);
this.lastBar = bar;
}
}
const trader = hft.createTrader(
config.FlowTdPath,
config.FrontTdAddrs,
config.UserInfo,
);
const market = hft.createMarket(config.FlowMdPath, config.FrontMdAddrs);
const enableRecorder = false;
if (enableRecorder) {
market.setRecorder(
{
onMarketData: (marketData: ctp.DepthMarketDataField) => {
console.log(marketData.InstrumentID, marketData.LastPrice);
},
},
(instruments) =>
instruments
.filter((instrument) => instrument.productType === "futures")
.map((instrument) => instrument.symbol),
);
}
const broker = hft.createBroker(trader, market, {
onError(error, message) {
console.error(error, message);
},
});
broker.addStrategy(new Strategy(broker));
if (!broker.start()) {
console.error("Broker start failed");
exit(1);
}