shoonya-sdk 0.3.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.
- package/README.md +3 -0
- package/dist/shoonya-sdk.d.mts +335 -0
- package/dist/shoonya-sdk.d.ts +335 -0
- package/dist/shoonya-sdk.js +595 -0
- package/dist/shoonya-sdk.mjs +570 -0
- package/package.json +33 -0
- package/tsup.config.ts +12 -0
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/utils/sha256.ts
|
|
5
|
+
import crypto from "node:crypto";
|
|
6
|
+
function sha256(data) {
|
|
7
|
+
const hash = crypto.createHash("sha256");
|
|
8
|
+
return hash.update(data, "utf8").digest("hex");
|
|
9
|
+
}
|
|
10
|
+
__name(sha256, "sha256");
|
|
11
|
+
|
|
12
|
+
// src/paths.ts
|
|
13
|
+
var paths = {
|
|
14
|
+
login: "QuickAuth",
|
|
15
|
+
logout: "Logout",
|
|
16
|
+
forgotPassword: "ForgotPassword",
|
|
17
|
+
changePassword: "Changepwd",
|
|
18
|
+
watchlist: "MWList",
|
|
19
|
+
getWatchlist: "MarketWatch",
|
|
20
|
+
searchScrip: "SearchScrip",
|
|
21
|
+
userInfo: "UserDetails",
|
|
22
|
+
clientInfo: "ClientDetails",
|
|
23
|
+
quotes: "GetQuotes",
|
|
24
|
+
addScripToWL: "AddMultiScripsToMW",
|
|
25
|
+
removeScripFromWL: "DeleteMultiMWScrips",
|
|
26
|
+
securityInfo: "GetSecurityInfo",
|
|
27
|
+
placeOrder: "PlaceOrder",
|
|
28
|
+
modifyOrder: "ModifyOrder",
|
|
29
|
+
cancelOrder: "CancelOrder",
|
|
30
|
+
exitSNOOrder: "ExitSNOOrder",
|
|
31
|
+
historicData: "TPSeries",
|
|
32
|
+
optionChain: "GetOptionChain",
|
|
33
|
+
orderBook: "OrderBook"
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// src/shoonya-sdk.ts
|
|
37
|
+
import { WebSocket as WS } from "ws";
|
|
38
|
+
import { EventEmitter } from "events";
|
|
39
|
+
import cron from "node-cron";
|
|
40
|
+
|
|
41
|
+
// src/utils/logger.ts
|
|
42
|
+
import {
|
|
43
|
+
statSync,
|
|
44
|
+
mkdirSync,
|
|
45
|
+
existsSync,
|
|
46
|
+
writeFileSync,
|
|
47
|
+
appendFileSync,
|
|
48
|
+
readdirSync,
|
|
49
|
+
readFileSync,
|
|
50
|
+
unlinkSync
|
|
51
|
+
} from "fs";
|
|
52
|
+
import { dirname, join } from "path";
|
|
53
|
+
var Logger = class {
|
|
54
|
+
static {
|
|
55
|
+
__name(this, "Logger");
|
|
56
|
+
}
|
|
57
|
+
logPath;
|
|
58
|
+
maxSize;
|
|
59
|
+
/**
|
|
60
|
+
*
|
|
61
|
+
* @param logPath path of the log file
|
|
62
|
+
* @param maxSize max size a log file should have (in bytes)
|
|
63
|
+
*/
|
|
64
|
+
constructor(logPath, maxSize) {
|
|
65
|
+
this.logPath = logPath;
|
|
66
|
+
this.maxSize = maxSize;
|
|
67
|
+
if (!existsSync(this.logPath)) {
|
|
68
|
+
mkdirSync(dirname(this.logPath), { recursive: true });
|
|
69
|
+
writeFileSync(this.logPath, "", "utf8");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
checkLogSize() {
|
|
73
|
+
const dirName = dirname(this.logPath);
|
|
74
|
+
try {
|
|
75
|
+
const stats = statSync(this.logPath);
|
|
76
|
+
if (stats.size >= this.maxSize) {
|
|
77
|
+
const rotatedPath = join(dirName, `log_${Date.now().toString()}.log`);
|
|
78
|
+
this.logPath = rotatedPath;
|
|
79
|
+
writeFileSync(rotatedPath, "", "utf-8");
|
|
80
|
+
}
|
|
81
|
+
} catch (err) {
|
|
82
|
+
throw new Error("file not found");
|
|
83
|
+
}
|
|
84
|
+
this.deleteOldestFile();
|
|
85
|
+
}
|
|
86
|
+
log(message, type = "INFO") {
|
|
87
|
+
this.checkLogSize();
|
|
88
|
+
const logMessage = `${type}: [${(/* @__PURE__ */ new Date()).toISOString()}]: ${message}
|
|
89
|
+
`;
|
|
90
|
+
appendFileSync(this.logPath, logMessage, { encoding: "utf-8" });
|
|
91
|
+
}
|
|
92
|
+
getRecentLog() {
|
|
93
|
+
const dir = readdirSync(dirname(this.logPath), { recursive: true });
|
|
94
|
+
const file = readFileSync(dir.at(-1), "utf8");
|
|
95
|
+
return file;
|
|
96
|
+
}
|
|
97
|
+
deleteOldestFile() {
|
|
98
|
+
const dirName = dirname(this.logPath);
|
|
99
|
+
const files = readdirSync(dirName);
|
|
100
|
+
const currentDate = /* @__PURE__ */ new Date();
|
|
101
|
+
const sevenDaysAgo = (/* @__PURE__ */ new Date()).setDate(currentDate.getDate() - 7);
|
|
102
|
+
for (const file of files) {
|
|
103
|
+
const filePath = join(dirName, file);
|
|
104
|
+
const stat = statSync(filePath);
|
|
105
|
+
if (stat.birthtimeMs < sevenDaysAgo) {
|
|
106
|
+
unlinkSync(filePath);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
var logger_default = Logger;
|
|
112
|
+
|
|
113
|
+
// src/shoonya-sdk.ts
|
|
114
|
+
import totp from "totp-generator";
|
|
115
|
+
var Shoonya = class extends EventEmitter {
|
|
116
|
+
static {
|
|
117
|
+
__name(this, "Shoonya");
|
|
118
|
+
}
|
|
119
|
+
accessToken;
|
|
120
|
+
userId;
|
|
121
|
+
logger;
|
|
122
|
+
logging = false;
|
|
123
|
+
httpBaseUrl = "https://api.shoonya.com/NorenWClientTP/";
|
|
124
|
+
wsBaseUrl = "wss://api.shoonya.com/NorenWSTP/";
|
|
125
|
+
ws;
|
|
126
|
+
connectTimer;
|
|
127
|
+
disconnectTimer;
|
|
128
|
+
accountId;
|
|
129
|
+
twoFa;
|
|
130
|
+
password;
|
|
131
|
+
apiKey;
|
|
132
|
+
cronJobRunning = false;
|
|
133
|
+
scripList;
|
|
134
|
+
constructor(options) {
|
|
135
|
+
const { logging = false } = options || {};
|
|
136
|
+
super({ captureRejections: true });
|
|
137
|
+
this.userId = "";
|
|
138
|
+
this.accessToken = "";
|
|
139
|
+
this.accountId = this.userId;
|
|
140
|
+
this.logging = logging;
|
|
141
|
+
if (logging) {
|
|
142
|
+
this.logger = new logger_default(
|
|
143
|
+
`./logs/log_${Date.now().toString()}.log`,
|
|
144
|
+
1024 * 10
|
|
145
|
+
//1KB * 10 = 10KB
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async request(path, body) {
|
|
150
|
+
const jData = `jData=${JSON.stringify(body.data)}${body.key ? "&jKey=" + body.key : ""}`;
|
|
151
|
+
try {
|
|
152
|
+
const data = await fetch(this.httpBaseUrl + paths[path], {
|
|
153
|
+
method: "POST",
|
|
154
|
+
body: jData,
|
|
155
|
+
keepalive: true
|
|
156
|
+
}).then((res) => {
|
|
157
|
+
return res.json();
|
|
158
|
+
});
|
|
159
|
+
return data;
|
|
160
|
+
} catch (err) {
|
|
161
|
+
if (this.logging) {
|
|
162
|
+
const errMessage = "Failed to fetch data from api while performing: " + path;
|
|
163
|
+
this.logger.log(errMessage, "ERROR");
|
|
164
|
+
console.error(errMessage);
|
|
165
|
+
}
|
|
166
|
+
console.error(err);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Rest API
|
|
170
|
+
/**
|
|
171
|
+
*
|
|
172
|
+
* @param credentials User Credential (username, password, appkeys etc.)
|
|
173
|
+
* @returns user information and access token
|
|
174
|
+
*/
|
|
175
|
+
async login(rawCred) {
|
|
176
|
+
const cred = {
|
|
177
|
+
apkversion: "1.0.0",
|
|
178
|
+
uid: rawCred.userId,
|
|
179
|
+
pwd: sha256(rawCred.password),
|
|
180
|
+
factor2: totp(rawCred.twoFa),
|
|
181
|
+
vc: rawCred?.vendorCode || rawCred.userId + "_U",
|
|
182
|
+
appkey: sha256(`${rawCred.userId}|${rawCred.apiKey}`),
|
|
183
|
+
imei: rawCred?.imei || "api",
|
|
184
|
+
source: "API"
|
|
185
|
+
};
|
|
186
|
+
try {
|
|
187
|
+
const req = await this.request("login", { data: cred });
|
|
188
|
+
this.userId = req.uid;
|
|
189
|
+
this.accessToken = req.susertoken;
|
|
190
|
+
this.accountId = this.userId;
|
|
191
|
+
this.apiKey = rawCred.apiKey;
|
|
192
|
+
this.password = rawCred.password;
|
|
193
|
+
this.twoFa = rawCred.twoFa;
|
|
194
|
+
return req;
|
|
195
|
+
} catch (err) {
|
|
196
|
+
if (this.logging) {
|
|
197
|
+
const fallBackMessage = "Attempted to login but it failed";
|
|
198
|
+
const errMessage = err?.message || fallBackMessage;
|
|
199
|
+
this.logger.log(errMessage, "ERROR");
|
|
200
|
+
console.error(errMessage);
|
|
201
|
+
}
|
|
202
|
+
console.error("Login Failed", err);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* @param query scrip name (BankNifty, Sensex etc.)
|
|
207
|
+
* @param exchange exchange name (NSE, BSE etc.)
|
|
208
|
+
* @returns
|
|
209
|
+
*/
|
|
210
|
+
async searchScrip(query, exchange) {
|
|
211
|
+
if (!this.accessToken) {
|
|
212
|
+
throw "Login First before seaching";
|
|
213
|
+
}
|
|
214
|
+
const data = {
|
|
215
|
+
uid: this.userId,
|
|
216
|
+
stext: query,
|
|
217
|
+
exch: exchange
|
|
218
|
+
};
|
|
219
|
+
return this.request("searchScrip", {
|
|
220
|
+
data,
|
|
221
|
+
key: this.accessToken
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
async getWatchlistsName() {
|
|
225
|
+
if (!this.accessToken) {
|
|
226
|
+
throw "Login First before accessing watchlists";
|
|
227
|
+
}
|
|
228
|
+
return this.request("watchlist", {
|
|
229
|
+
data: { uid: this.userId },
|
|
230
|
+
key: this.accessToken
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
async getWatchlist(listName) {
|
|
234
|
+
if (!this.accessToken) {
|
|
235
|
+
throw "Login First before accessing watchlists";
|
|
236
|
+
}
|
|
237
|
+
return this.request("getWatchlist", {
|
|
238
|
+
data: { uid: this.userId, wlname: listName },
|
|
239
|
+
key: this.accessToken
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
async forgetPassword(PAN, DOB) {
|
|
243
|
+
const data = {
|
|
244
|
+
uid: this.userId,
|
|
245
|
+
pan: PAN,
|
|
246
|
+
dob: DOB
|
|
247
|
+
};
|
|
248
|
+
return this.request("forgotPassword", { data });
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
*
|
|
252
|
+
* @param oldPass sha256 of old pass
|
|
253
|
+
* @param newPass new password in plain text
|
|
254
|
+
* @returns
|
|
255
|
+
*/
|
|
256
|
+
async changePassword(oldPass, newPass) {
|
|
257
|
+
const data = {
|
|
258
|
+
uid: this.userId,
|
|
259
|
+
oldpwd: oldPass,
|
|
260
|
+
pwd: newPass
|
|
261
|
+
};
|
|
262
|
+
return this.request("changePassword", { data });
|
|
263
|
+
}
|
|
264
|
+
async getUserDetails() {
|
|
265
|
+
return this.request("userInfo", {
|
|
266
|
+
data: { uid: this.userId },
|
|
267
|
+
key: this.accessToken
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* @param brokerId logged in user's brokers Name / Id
|
|
272
|
+
* @returns
|
|
273
|
+
*/
|
|
274
|
+
async getClientDetails(brokerId) {
|
|
275
|
+
const data = {
|
|
276
|
+
uid: this.userId,
|
|
277
|
+
actid: this.accountId,
|
|
278
|
+
brkname: brokerId
|
|
279
|
+
};
|
|
280
|
+
return this.request("clientInfo", { data, key: this.accessToken });
|
|
281
|
+
}
|
|
282
|
+
async getQuotes(exchange, contractToken) {
|
|
283
|
+
const data = {
|
|
284
|
+
uid: this.userId,
|
|
285
|
+
exch: exchange,
|
|
286
|
+
token: contractToken
|
|
287
|
+
};
|
|
288
|
+
return this.request("quotes", { data, key: this.accessToken });
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
*
|
|
292
|
+
* @param listName name of watchlist in which user want to add script
|
|
293
|
+
* @param scrips list of the scrip
|
|
294
|
+
* @returns
|
|
295
|
+
*/
|
|
296
|
+
async addScripToWatchList(listName, scrips) {
|
|
297
|
+
const data = {
|
|
298
|
+
uid: this.userId,
|
|
299
|
+
wlname: listName,
|
|
300
|
+
scrips: scrips.join("#")
|
|
301
|
+
};
|
|
302
|
+
return this.request("addScripToWL", { data, key: this.accessToken });
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
*
|
|
306
|
+
* @param listName name of watchlist in which user want to add script
|
|
307
|
+
* @param scrips list of the scrip
|
|
308
|
+
* @returns
|
|
309
|
+
*/
|
|
310
|
+
async removeScripFromWatchList(listName, scrips) {
|
|
311
|
+
const data = {
|
|
312
|
+
uid: this.userId,
|
|
313
|
+
wlname: listName,
|
|
314
|
+
scrips: scrips.join("#")
|
|
315
|
+
};
|
|
316
|
+
return this.request("removeScripFromWL", { data, key: this.accessToken });
|
|
317
|
+
}
|
|
318
|
+
async getSecurityInfo(exchange, contractToken) {
|
|
319
|
+
const data = {
|
|
320
|
+
uid: this.userId,
|
|
321
|
+
exch: exchange,
|
|
322
|
+
token: contractToken
|
|
323
|
+
};
|
|
324
|
+
return this.request("securityInfo", { data, key: this.accessToken });
|
|
325
|
+
}
|
|
326
|
+
async placeOrder(details) {
|
|
327
|
+
const data = {
|
|
328
|
+
uid: details.uid || this.userId,
|
|
329
|
+
actid: details.actid || this.accountId,
|
|
330
|
+
...details
|
|
331
|
+
};
|
|
332
|
+
return this.request("placeOrder", {
|
|
333
|
+
data,
|
|
334
|
+
key: this.accessToken
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
async modifyOrder(orderDetail) {
|
|
338
|
+
orderDetail.uid ??= this.userId;
|
|
339
|
+
orderDetail.actid ??= this.accountId;
|
|
340
|
+
return this.request("modifyOrder", {
|
|
341
|
+
data: orderDetail,
|
|
342
|
+
key: this.accessToken
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
async cancelOrder(orderNo) {
|
|
346
|
+
return this.request("cancelOrder", {
|
|
347
|
+
data: {
|
|
348
|
+
uid: this.userId,
|
|
349
|
+
norenordno: orderNo
|
|
350
|
+
},
|
|
351
|
+
key: this.accessToken
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
*
|
|
356
|
+
* @param orderNo Noren order number, which needs to be modified
|
|
357
|
+
* @param product Allowed for only H and B products (Cover order and bracket order)
|
|
358
|
+
* @returns
|
|
359
|
+
*/
|
|
360
|
+
async exitSNOOrder(orderNo, product) {
|
|
361
|
+
const data = {
|
|
362
|
+
norenordno: orderNo,
|
|
363
|
+
prd: product,
|
|
364
|
+
uid: this.userId
|
|
365
|
+
};
|
|
366
|
+
return this.request("exitSNOOrder", { data, key: this.accessToken });
|
|
367
|
+
}
|
|
368
|
+
async getHistoricData(option) {
|
|
369
|
+
const data = { uid: this.userId, ...option };
|
|
370
|
+
return this.request("historicData", { data, key: this.accessToken });
|
|
371
|
+
}
|
|
372
|
+
async getOptionChain(option) {
|
|
373
|
+
const data = {
|
|
374
|
+
uid: this.userId,
|
|
375
|
+
tsym: encodeURIComponent(option.tradingSymbol),
|
|
376
|
+
exch: option.exchange,
|
|
377
|
+
strprc: option.midPrice,
|
|
378
|
+
cnt: option.count
|
|
379
|
+
};
|
|
380
|
+
return this.request("optionChain", {
|
|
381
|
+
data,
|
|
382
|
+
key: this.accessToken
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
async orderBook(prd) {
|
|
386
|
+
const data = { uid: this.userId, prd };
|
|
387
|
+
return this.request("orderBook", {
|
|
388
|
+
data,
|
|
389
|
+
key: this.accessToken
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Its a custom method which will return token number of option
|
|
394
|
+
* it can be used to start websocket connection to that tokenƒ
|
|
395
|
+
*
|
|
396
|
+
* @param params info about symbol
|
|
397
|
+
* @returns
|
|
398
|
+
*/
|
|
399
|
+
async getOptionToken(params) {
|
|
400
|
+
const { strikePrice, exchange = "NFO", count = "5" } = params;
|
|
401
|
+
const optionType = params.optionType.at(0);
|
|
402
|
+
const symbol = params.symbol.toUpperCase();
|
|
403
|
+
for await (const expiry of this.getNext7FormattedDate()) {
|
|
404
|
+
const optionToPick = `${symbol}${expiry}${optionType}${strikePrice}`;
|
|
405
|
+
const optionChain = await this.getOptionChain({
|
|
406
|
+
count,
|
|
407
|
+
exchange,
|
|
408
|
+
midPrice: strikePrice.toString(),
|
|
409
|
+
tradingSymbol: optionToPick
|
|
410
|
+
});
|
|
411
|
+
if (optionChain.stat === "Not_Ok")
|
|
412
|
+
continue;
|
|
413
|
+
for (const { tsym, token } of optionChain.values) {
|
|
414
|
+
if (tsym === optionToPick) {
|
|
415
|
+
return { token, exchange, tsym };
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
getFormattedExpiry(date) {
|
|
422
|
+
const stringDate = date.toString();
|
|
423
|
+
const [_, month, day, year] = stringDate.split(" ");
|
|
424
|
+
const newMonth = month.toUpperCase();
|
|
425
|
+
const newYear = year.slice(2);
|
|
426
|
+
return `${day}${newMonth}${newYear}`;
|
|
427
|
+
}
|
|
428
|
+
getNext7FormattedDate() {
|
|
429
|
+
const dates = [];
|
|
430
|
+
let currentDate = /* @__PURE__ */ new Date();
|
|
431
|
+
for (let i = 0; i < 7; i++) {
|
|
432
|
+
const formattedDate = this.getFormattedExpiry(currentDate);
|
|
433
|
+
dates.push(formattedDate);
|
|
434
|
+
currentDate = new Date(currentDate.setDate(currentDate.getDate() + 1));
|
|
435
|
+
}
|
|
436
|
+
return dates;
|
|
437
|
+
}
|
|
438
|
+
// WebSocket API
|
|
439
|
+
connectWS() {
|
|
440
|
+
if (this.ws && (this.ws.readyState === 0 || this.ws.readyState === 1)) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
this.ws = new WS(this.wsBaseUrl);
|
|
444
|
+
this.ws.addEventListener("open", () => {
|
|
445
|
+
let msg = {
|
|
446
|
+
t: "c",
|
|
447
|
+
uid: this.userId,
|
|
448
|
+
actid: this.accountId,
|
|
449
|
+
source: "API",
|
|
450
|
+
susertoken: this.accessToken
|
|
451
|
+
};
|
|
452
|
+
this.ws.send(JSON.stringify(msg));
|
|
453
|
+
if (this.cronJobRunning) {
|
|
454
|
+
this.subscribe(this.scripList);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
cron.schedule("43,00 3,10 * * 1-5", async () => {
|
|
458
|
+
this.cronJobRunning = true;
|
|
459
|
+
const hour = (/* @__PURE__ */ new Date()).getUTCHours();
|
|
460
|
+
const min = (/* @__PURE__ */ new Date()).getUTCMinutes();
|
|
461
|
+
if (hour === 3 && min === 43) {
|
|
462
|
+
try {
|
|
463
|
+
const cred = {
|
|
464
|
+
apiKey: this.apiKey,
|
|
465
|
+
password: this.password,
|
|
466
|
+
twoFa: this.twoFa,
|
|
467
|
+
userId: this.userId
|
|
468
|
+
};
|
|
469
|
+
await this.login(cred);
|
|
470
|
+
this.connectWS();
|
|
471
|
+
} catch (err) {
|
|
472
|
+
console.error(err);
|
|
473
|
+
const errMessage = `Token refreshing failed. ERR: ${err.message}`;
|
|
474
|
+
this.logging && this.logger.log(errMessage, "ERROR");
|
|
475
|
+
this.emit("stopped", errMessage);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (hour === 10 && min === 0) {
|
|
479
|
+
this.disconnect();
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
this.ws.addEventListener("message", (e) => {
|
|
484
|
+
const result = JSON.parse(e.data.toString());
|
|
485
|
+
if (result.t === "dk") {
|
|
486
|
+
this.logging && this.logger.log(`Subscribed to ${result.ts}. Listening...`);
|
|
487
|
+
}
|
|
488
|
+
if (result.lp && result.t === "df") {
|
|
489
|
+
this.emit("priceUpdate", result);
|
|
490
|
+
}
|
|
491
|
+
if (result.t === "ok") {
|
|
492
|
+
this.emit("orderUpdate", result);
|
|
493
|
+
}
|
|
494
|
+
if (result.t === "ck") {
|
|
495
|
+
clearTimeout(this.connectTimer);
|
|
496
|
+
this.connectTimer = setTimeout(() => {
|
|
497
|
+
this.emit("connect", e);
|
|
498
|
+
this.logging && this.logger.log("Connected with Shoonya API! Streaming Data...");
|
|
499
|
+
}, 3e3);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
clearTimeout(this.disconnectTimer);
|
|
503
|
+
this.disconnectTimer = setTimeout(() => {
|
|
504
|
+
const day = (/* @__PURE__ */ new Date()).getDay();
|
|
505
|
+
if (day in [5, 6])
|
|
506
|
+
return;
|
|
507
|
+
const err = "API have stopped to respond! there could be a problem with API or market has closed";
|
|
508
|
+
this.logging && this.logger.log(err, "WARN");
|
|
509
|
+
this.emit("stopped", err);
|
|
510
|
+
}, 6e4 * 60 * 18 - 15e3);
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
*
|
|
515
|
+
* subscribes for list of scrip by which WS returns them every time their price changes.
|
|
516
|
+
*
|
|
517
|
+
* `searchScrip` method can be used to get scripToken from scripName
|
|
518
|
+
* @param scripList scrip list requires list of scrip name in format of `exchangeName|scripToken`
|
|
519
|
+
*/
|
|
520
|
+
subscribe(scripList) {
|
|
521
|
+
const msg = {
|
|
522
|
+
t: "d",
|
|
523
|
+
// depth subscription
|
|
524
|
+
k: scripList.join("#")
|
|
525
|
+
};
|
|
526
|
+
this.scripList = scripList;
|
|
527
|
+
if (this.ws) {
|
|
528
|
+
this.ws.send(JSON.stringify(msg));
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
unsubscribe(scripList) {
|
|
532
|
+
const msg = {
|
|
533
|
+
t: "ud",
|
|
534
|
+
// unsubcribe depth subscription
|
|
535
|
+
k: scripList.join("#")
|
|
536
|
+
};
|
|
537
|
+
if (this.ws) {
|
|
538
|
+
this.ws.send(JSON.stringify(msg));
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
subscribeOrderUpdate() {
|
|
542
|
+
const msg = {
|
|
543
|
+
t: "o",
|
|
544
|
+
// order update
|
|
545
|
+
actid: this.userId
|
|
546
|
+
};
|
|
547
|
+
if (this.ws) {
|
|
548
|
+
this.ws.send(JSON.stringify(msg));
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
unsubscribeOrderUpdate() {
|
|
552
|
+
const msg = {
|
|
553
|
+
t: "ud"
|
|
554
|
+
// unsubscribe order update
|
|
555
|
+
};
|
|
556
|
+
if (this.ws) {
|
|
557
|
+
this.ws.send(JSON.stringify(msg));
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
disconnect() {
|
|
561
|
+
if (this.ws && this.ws.readyState !== 2 && this.ws.readyState !== 3) {
|
|
562
|
+
this.ws.close();
|
|
563
|
+
this.logging && this.logger.log("Websocket Connection with Shoonya API is now closed!");
|
|
564
|
+
this.emit("close");
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
export {
|
|
569
|
+
Shoonya
|
|
570
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shoonya-sdk",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "Wrapper around Shoonya API",
|
|
5
|
+
"main": "dist/shoonya-sdk.js",
|
|
6
|
+
"module": "dist/shoonya-sdk.mjs",
|
|
7
|
+
"types": "dist/shoonya-sdk.d.ts",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"SDK",
|
|
10
|
+
"Shoonya",
|
|
11
|
+
"Trading"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"lint": "tsc"
|
|
16
|
+
},
|
|
17
|
+
"author": "cryogon",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^20.3.1",
|
|
21
|
+
"@types/node-cron": "^3.0.8",
|
|
22
|
+
"@types/totp-generator": "^0.0.5",
|
|
23
|
+
"@types/ws": "^8.5.5",
|
|
24
|
+
"tsup": "^7.1.0",
|
|
25
|
+
"typescript": "^5.1.3",
|
|
26
|
+
"bun-types": "latest"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"node-cron": "^3.0.2",
|
|
30
|
+
"totp-generator": "^0.0.14",
|
|
31
|
+
"ws": "^8.13.0"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/tsup.config.ts
ADDED