Skip to content

Instantly share code, notes, and snippets.

@exvion
Last active October 26, 2023 19:46
Show Gist options
  • Save exvion/edf4c0b44c9b8dcd6a193f0318a54a7f to your computer and use it in GitHub Desktop.
Save exvion/edf4c0b44c9b8dcd6a193f0318a54a7f to your computer and use it in GitHub Desktop.
client on NodeJS for QuikSharp
import { QuikClient } from './QuikClient.mjs'
const qc = new QuikClient({
host: "10.211.55.3",
port: 34150
});
qc.connect();
qc.on("connect", () => {
qc.subscribe("SPBRU_USD|SPBE")
.then((data) => { console.log(data) });
// qc.sendTransaction(0, 0)
// .then((data)=> { console.log(data) })
// .catch((error) => {console.log(error)});
// qc.sendOrder("SPBRU_USD|SPBE", 0, 0)
// .then((order) => {console.log(new Date().toISOString() + " " + order.order_num) })
// .catch((error) => {console.log(error)});
});
qc.on("onQuote", (quote) => console.log(quote);
import net from 'net';
import { EventEmitter } from 'events';
import iconv from 'iconv-lite';
import { inspect } from 'util';
export class OrderTradeFlags {
static Active = 0x1;
static Canceled = 0x2;
static IsSell = 0x4;
static IsLimit = 0x8;
static AllowDiffPrice = 0x10;
static FillOrKill = 0x20;
static IsMarketMakerOrSent = 0x40;
static IsReceived = 0x80;
static IsKillBalance = 0x100;
static Iceberg = 0x200;
}
export class QuikClient extends EventEmitter {
#options = {};
#CRLF = '\r\n\r\n';
responseClient = new net.Socket();
callbackClient = new net.Socket();
#chunk = "";
#message = "";
#transactionID = 0;
#correlationId = 0;
constructor(options = {}) {
super();
this.#options = options;
this.responseClient.on('connect', () => {
this.log(`Connected to responseClient`);
this.callbackClient.on('connect', () => {
this.log(`Connected to callbackClient`);
this.emit('connect');
})
});
this.responseClient.on('error', (error) => {
this.emit('error', error);
});
this.callbackClient.on('data', (data) => {
this.#handleCallbackClientData(data);
});
this.responseClient.on('data', (data) => {
this.#responseTask(data);
});
this.callbackClient.on('end', () => {
console.log("end");
});
this.on("json", (json) => {
// console.log("json");
});
}
#responseTask(data) {
this.#message += iconv.decode(data, 'windows-1251');
if (this.#message.indexOf("\n") <= 0) return;
const messages = this.#message.split("\n")
messages.map((message) => {
if (!message.trim()) return;
let data;
try {
data = JSON.parse(message.trim())
} catch (error) {
console.log('Error while parsing quik data: ' + message);
console.log(error);
return;
}
if (!data) {
console.log('parsed empty data object')
return;
}
if (!data.hasOwnProperty('cmd') || !data.hasOwnProperty('data')) {
console.log('wrong data');
return;
}
this.emit("onMessage", data);
})
this.#message = '';
}
#handleCallbackClientData(data) {
this.#chunk += iconv.decode(data, 'windows-1251');
if (this.#chunk.indexOf("\n") <= 0) return;
const messages = this.#chunk.split("\n")
messages.map((message) => {
if (!message.trim()) return;
let data;
try {
data = JSON.parse(message.trim())
} catch (error) {
console.log('Error while parsing quik data: ' + message);
console.log(error);
return;
}
if (!data) {
console.log('parsed empty data object')
return;
}
if (!data.hasOwnProperty('cmd') || !data.hasOwnProperty('data')) {
console.log('wrong data');
return;
}
this.#handleMessage(data);
})
this.#message = '';
}
#handleMessage(message) {
switch (message.cmd) {
case 'OnQuote':
this.emit('onQuote', message.data);
break;
case 'OnTrade':
this.emit('onTrade', message.data);
break;
case 'OnParam':
this.emit('onParam', message.data);
break;
case 'OnOrder':
this.emit('onOrder', message.data);
break;
case 'OnTransReply':
this.emit('onTransReply', message.data);
break;
case 'OnFirm':
this.emit('onFirm', message.data);
break;
case 'OnDepoLimit':
this.emit('onDepoLimit', message.data);
break;
case 'OnAccountPosition':
this.emit('onAccountPosition', message.data);
break;
case 'OnMoneyLimit':
this.emit('onMoneyLimit', message.data);
break;
default:
console.log(message);
}
}
#getNewUniqueId() {
this.#correlationId++;
let newId = this.#correlationId;
if (newId > 0) {
return newId;
}
this.#correlationId = 1;
return 1;
}
#send(request) {
request.id = this.#getNewUniqueId();
return new Promise((resolve, reject) => {
let raw_data = JSON.stringify(request) + this.#CRLF;
this.responseClient.write(raw_data);
this.on('onMessage', (message) => {
if (message["id"] === request.id) {
resolve(message);
}
});
this.responseClient.on('error', (err) => {
reject(err);
});
});
}
getQuoteLevel2(symbol) {
return this.#send({ "data": symbol, "cmd": "GetQuoteLevel2", "t": "" });
}
subscribe(symbol) {
return this.#send({ "data": symbol, "cmd": "Subscribe_Level_II_Quotes", "t": "" });
}
getOrders() {
return this.#send({ "data": "SPBRU_USD|SPBE", "cmd": "get_orders", "t": "" });
}
getUniqueTransactionId() {
return ++this.#transactionID;
}
sendTransaction(classCode, secCode, price, volume) {
let id = this.#getNewUniqueId();
let transId = this.getUniqueTransactionId().toString();
let request = { "id": id, "cmd": "sendTransaction", "t": "" };
request.data = {
ACTION: "NEW_ORDER",
CLASSCODE: classCode,
SECCODE: secCode,
ACCOUNT: "SP01-CL00000",
CLIENT_CODE: "123456",
OPERATION: "B",
PRICE: price.toString(),
QUANTITY: volume.toString(),
TRANS_ID: transId,
EXECUTION_CONDITION: "PUT_IN_QUEUE"
}
let raw_data = JSON.stringify(request) + this.#CRLF;
return new Promise((resolve, reject) => {
this.responseClient.write(raw_data);
this.on('onMessage', (message) => {
console.log(message);
if (message["id"] === id) {
if (message["lua_error"] !== undefined) {
reject(message["lua_error"]);
}
}
});
this.on("onTransReply", (transReply) => {
if (transReply.trans_id == transId && transReply.status == 6) { //Скорректированное значение НПР1 -68276.05 (RUB) меньше 0
reject(transReply.result_msg);
} else {
resolve(transId);
}
})
this.responseClient.on('error', (err) => {
reject(err);
});
});
}
ping() {
console.log("pong");
}
//symbol = "SPBXM|TCS"
sendOrder(symbol, price, volume) {
let [classCode, secCode] = symbol.split('|', 2);
console.log(secCode + " " + secCode);
return new Promise((resolve, reject) => {
this.sendTransaction(classCode, secCode, price, volume)
.then((transId) => {
this.on('onOrder', (order) => {
if (order.trans_id == transId) {
order.flags & OrderTradeFlags.Active
? console.log("active")
: (order.flags & OrderTradeFlags.Canceled
? console.log("cancelled") : console.log("completed")
)
console.log(order.flags);
resolve(order);
}
})
})
.catch((error) => {
reject(error);
});
})
}
connect() {
this.log(`Connecting to ${this.#options.host}:${this.#options.port}...`);
this.responseClient.connect(this.#options.port, this.#options.host);
this.responseClient.on('connect', () => {
this.callbackClient.connect(this.#options.port + 1, this.#options.host);
});
}
log(message) {
console.log(inspect(message, { depth: 10 }));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment