nsekit 0.3.0 → 0.3.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/README.md +4 -4
- package/dist/brokers/dhan/DhanBroker.d.ts +14 -0
- package/dist/brokers/dhan/DhanBroker.d.ts.map +1 -1
- package/dist/brokers/dhan/dhan-auth.d.ts +33 -4
- package/dist/brokers/dhan/dhan-auth.d.ts.map +1 -1
- package/dist/brokers/dhan/dhan-constants.d.ts +1 -0
- package/dist/brokers/dhan/dhan-constants.d.ts.map +1 -1
- package/dist/brokers/dhan/dhan-instruments.d.ts.map +1 -1
- package/dist/brokers/dhan/dhan-mapper.d.ts +1 -0
- package/dist/brokers/dhan/dhan-mapper.d.ts.map +1 -1
- package/dist/brokers/dhan/dhan-socket.d.ts +41 -3
- package/dist/brokers/dhan/dhan-socket.d.ts.map +1 -1
- package/dist/brokers/finvasia/FinvasiaBroker.d.ts +8 -0
- package/dist/brokers/finvasia/FinvasiaBroker.d.ts.map +1 -1
- package/dist/brokers/finvasia/finvasia-constants.d.ts +1 -1
- package/dist/brokers/finvasia/finvasia-constants.d.ts.map +1 -1
- package/dist/brokers/finvasia/finvasia-instruments.d.ts +1 -1
- package/dist/brokers/finvasia/finvasia-instruments.d.ts.map +1 -1
- package/dist/brokers/finvasia/finvasia-mapper.d.ts +5 -1
- package/dist/brokers/finvasia/finvasia-mapper.d.ts.map +1 -1
- package/dist/brokers/finvasia/finvasia-socket.d.ts +8 -2
- package/dist/brokers/finvasia/finvasia-socket.d.ts.map +1 -1
- package/dist/index.js +412 -99
- package/dist/instruments/instrument-master.d.ts.map +1 -1
- package/package.json +12 -3
package/dist/index.js
CHANGED
|
@@ -19448,8 +19448,9 @@ class InstrumentMaster {
|
|
|
19448
19448
|
if (local.ok)
|
|
19449
19449
|
return local;
|
|
19450
19450
|
if (brokerInstruments.capabilities.searchAPI && brokerInstruments.searchAPI) {
|
|
19451
|
-
const { symbol } = this.parseInput(input);
|
|
19452
|
-
const
|
|
19451
|
+
const { exchange, symbol } = this.parseInput(input);
|
|
19452
|
+
const inferredExchange = exchange ?? (/-(?:CE|PE|FUT)-/.test(symbol) ? "NFO" : undefined);
|
|
19453
|
+
const apiResult = await brokerInstruments.searchAPI(symbol, inferredExchange);
|
|
19453
19454
|
if (apiResult.ok && apiResult.value.length > 0) {
|
|
19454
19455
|
for (const entry of apiResult.value)
|
|
19455
19456
|
this.mergeEntry(brokerId, entry);
|
|
@@ -20192,8 +20193,8 @@ function safeSide(raw) {
|
|
|
20192
20193
|
}
|
|
20193
20194
|
function parseTimestamp(ts) {
|
|
20194
20195
|
if (!ts)
|
|
20195
|
-
return
|
|
20196
|
-
return new Date(ts).getTime();
|
|
20196
|
+
return 0;
|
|
20197
|
+
return new Date(ts).getTime() || 0;
|
|
20197
20198
|
}
|
|
20198
20199
|
function inferInstrumentType(symbol, exchange) {
|
|
20199
20200
|
if (exchange === "NSE" || exchange === "BSE")
|
|
@@ -21012,7 +21013,7 @@ var VALIDITY_FROM_SHOONYA = {
|
|
|
21012
21013
|
IOC: "IOC"
|
|
21013
21014
|
};
|
|
21014
21015
|
var SHOONYA_API_BASE = "https://api.shoonya.com/NorenWClientTP";
|
|
21015
|
-
var SHOONYA_WS_URL = "wss://api.shoonya.com/NorenWSTP";
|
|
21016
|
+
var SHOONYA_WS_URL = "wss://api.shoonya.com/NorenWSTP/";
|
|
21016
21017
|
var SHOONYA_INSTRUMENTS_BASE = "https://api.shoonya.com/";
|
|
21017
21018
|
var SHOONYA_INSTRUMENT_FILES = {
|
|
21018
21019
|
NSE: "NSE_symbols.txt.zip",
|
|
@@ -21169,13 +21170,18 @@ function inferSide2(qty) {
|
|
|
21169
21170
|
}
|
|
21170
21171
|
function parseTimestamp2(ts) {
|
|
21171
21172
|
if (!ts)
|
|
21172
|
-
return
|
|
21173
|
+
return 0;
|
|
21173
21174
|
const asNum = Number(ts);
|
|
21174
21175
|
if (!isNaN(asNum) && asNum > 1000000000000)
|
|
21175
21176
|
return asNum;
|
|
21176
21177
|
if (!isNaN(asNum) && asNum > 1e9)
|
|
21177
21178
|
return asNum * 1000;
|
|
21178
|
-
|
|
21179
|
+
const shoonya = ts.match(/^(\d{2}:\d{2}:\d{2})\s+(\d{2})-(\d{2})-(\d{4})$/);
|
|
21180
|
+
if (shoonya) {
|
|
21181
|
+
const [, time, dd, mm, yyyy] = shoonya;
|
|
21182
|
+
return new Date(`${yyyy}-${mm}-${dd}T${time}`).getTime() || 0;
|
|
21183
|
+
}
|
|
21184
|
+
return new Date(ts).getTime() || 0;
|
|
21179
21185
|
}
|
|
21180
21186
|
function toOrderParams2(unified, uid, actid) {
|
|
21181
21187
|
const params = {
|
|
@@ -21203,17 +21209,21 @@ function fromPosition2(pos) {
|
|
|
21203
21209
|
const buyQty = num(pos.daybuyqty);
|
|
21204
21210
|
const sellQty = num(pos.daysellqty);
|
|
21205
21211
|
const realizedPnl = num(pos.rpnl);
|
|
21206
|
-
const
|
|
21212
|
+
const ltp = num(pos.lp);
|
|
21213
|
+
const entryPrice = num(pos.upldprc);
|
|
21214
|
+
const avgPrice = entryPrice > 0 ? entryPrice : num(pos.netavgprc);
|
|
21215
|
+
const unrealizedPnl = entryPrice > 0 ? (ltp - entryPrice) * netQty : num(pos.urmtom);
|
|
21216
|
+
const tradingSymbol = pos.tsym.replace(/-EQ$|-BE$|-BL$|-IL$|-IQ$/, "");
|
|
21207
21217
|
return {
|
|
21208
21218
|
broker: "finvasia",
|
|
21209
|
-
tradingSymbol
|
|
21219
|
+
tradingSymbol,
|
|
21210
21220
|
exchange: safeExchange2(pos.exch),
|
|
21211
21221
|
side: inferSide2(netQty),
|
|
21212
21222
|
quantity: Math.abs(netQty),
|
|
21213
21223
|
buyQty,
|
|
21214
21224
|
sellQty,
|
|
21215
|
-
avgPrice
|
|
21216
|
-
ltp
|
|
21225
|
+
avgPrice,
|
|
21226
|
+
ltp,
|
|
21217
21227
|
pnl: realizedPnl + unrealizedPnl,
|
|
21218
21228
|
realizedPnl,
|
|
21219
21229
|
unrealizedPnl,
|
|
@@ -21250,7 +21260,7 @@ function fromTick2(tick, symbolLookup) {
|
|
|
21250
21260
|
return {
|
|
21251
21261
|
broker: "finvasia",
|
|
21252
21262
|
token: tick.tk,
|
|
21253
|
-
tradingSymbol: symbolLookup ?? tick.tk,
|
|
21263
|
+
tradingSymbol: symbolLookup ?? tick.ts ?? tick.tk,
|
|
21254
21264
|
ltp: num(tick.lp),
|
|
21255
21265
|
open: num(tick.o),
|
|
21256
21266
|
high: num(tick.h),
|
|
@@ -21265,15 +21275,17 @@ function fromTick2(tick, symbolLookup) {
|
|
|
21265
21275
|
timestamp: parseTimestamp2(tick.ft)
|
|
21266
21276
|
};
|
|
21267
21277
|
}
|
|
21268
|
-
function fromHolding2(holding) {
|
|
21278
|
+
function fromHolding2(holding, ltp = 0) {
|
|
21269
21279
|
const first = holding.exch_tsym?.[0];
|
|
21270
21280
|
const qty = num(holding.holdqty) + num(holding.dpqty) + num(holding.npoadqty) + num(holding.btstqty) - num(holding.usedqty);
|
|
21271
21281
|
const avgPrice = num(holding.upldprc);
|
|
21272
|
-
|
|
21282
|
+
ltp = num(holding.lp) || num(holding.c) || ltp;
|
|
21273
21283
|
const pnl = (ltp - avgPrice) * qty;
|
|
21284
|
+
const rawSymbol = first?.tsym ?? "";
|
|
21285
|
+
const tradingSymbol = rawSymbol.replace(/-EQ$|-BE$|-BL$|-IL$|-IQ$/, "");
|
|
21274
21286
|
return {
|
|
21275
21287
|
broker: "finvasia",
|
|
21276
|
-
tradingSymbol
|
|
21288
|
+
tradingSymbol,
|
|
21277
21289
|
exchange: safeExchange2(first?.exch ?? "NSE"),
|
|
21278
21290
|
isin: holding.isin ?? "",
|
|
21279
21291
|
quantity: qty,
|
|
@@ -21312,6 +21324,7 @@ function fromQuote2(quote) {
|
|
|
21312
21324
|
}
|
|
21313
21325
|
|
|
21314
21326
|
// src/brokers/finvasia/finvasia-socket.ts
|
|
21327
|
+
import WS from "ws";
|
|
21315
21328
|
var subIdCounter3 = 0;
|
|
21316
21329
|
function nextSubId3() {
|
|
21317
21330
|
subIdCounter3 += 1;
|
|
@@ -21329,21 +21342,24 @@ class FinvasiaSocket {
|
|
|
21329
21342
|
reconnectAttempts = 0;
|
|
21330
21343
|
maxReconnects = 5;
|
|
21331
21344
|
reconnectTimer = null;
|
|
21332
|
-
|
|
21345
|
+
onAuthFailed;
|
|
21346
|
+
constructor(userId, accessToken, onAuthFailed) {
|
|
21333
21347
|
this.userId = userId;
|
|
21334
21348
|
this.accessToken = accessToken;
|
|
21349
|
+
this.onAuthFailed = onAuthFailed;
|
|
21335
21350
|
}
|
|
21336
21351
|
connect() {
|
|
21337
21352
|
if (this.state === "CONNECTED" || this.state === "CONNECTING")
|
|
21338
21353
|
return;
|
|
21339
21354
|
this.state = "CONNECTING";
|
|
21340
21355
|
try {
|
|
21341
|
-
this.ws = new
|
|
21356
|
+
this.ws = new WS(SHOONYA_WS_URL);
|
|
21342
21357
|
} catch {
|
|
21343
21358
|
this.state = "DISCONNECTED";
|
|
21344
21359
|
return;
|
|
21345
21360
|
}
|
|
21346
|
-
this.ws.
|
|
21361
|
+
this.ws.on("open", () => {
|
|
21362
|
+
console.log(`[FinvasiaSocket] WS open, sending auth for uid=${this.userId}`);
|
|
21347
21363
|
const authMsg = JSON.stringify({
|
|
21348
21364
|
t: "c",
|
|
21349
21365
|
uid: this.userId,
|
|
@@ -21352,18 +21368,23 @@ class FinvasiaSocket {
|
|
|
21352
21368
|
source: "API"
|
|
21353
21369
|
});
|
|
21354
21370
|
this.ws.send(authMsg);
|
|
21355
|
-
};
|
|
21356
|
-
this.ws.
|
|
21371
|
+
});
|
|
21372
|
+
this.ws.on("message", (raw) => {
|
|
21357
21373
|
try {
|
|
21358
|
-
const
|
|
21374
|
+
const text = raw.toString();
|
|
21375
|
+
console.log(`[FinvasiaSocket] WS msg: ${text.slice(0, 200)}`);
|
|
21376
|
+
const data = JSON.parse(text);
|
|
21359
21377
|
this.handleMessage(data);
|
|
21360
21378
|
} catch {}
|
|
21361
|
-
};
|
|
21362
|
-
this.ws.
|
|
21379
|
+
});
|
|
21380
|
+
this.ws.on("close", (code, reason) => {
|
|
21381
|
+
console.error(`[FinvasiaSocket] WS closed: code=${code}, reason=${reason.toString()}`);
|
|
21363
21382
|
this.state = "DISCONNECTED";
|
|
21364
21383
|
this.attemptReconnect();
|
|
21365
|
-
};
|
|
21366
|
-
this.ws.
|
|
21384
|
+
});
|
|
21385
|
+
this.ws.on("error", (err) => {
|
|
21386
|
+
console.error(`[FinvasiaSocket] WS error:`, err.message);
|
|
21387
|
+
});
|
|
21367
21388
|
}
|
|
21368
21389
|
disconnect() {
|
|
21369
21390
|
this.clearReconnectTimer();
|
|
@@ -21375,6 +21396,18 @@ class FinvasiaSocket {
|
|
|
21375
21396
|
this.state = "DISCONNECTED";
|
|
21376
21397
|
this.subscribedTokens.clear();
|
|
21377
21398
|
}
|
|
21399
|
+
reconnect(newToken) {
|
|
21400
|
+
this.clearReconnectTimer();
|
|
21401
|
+
if (newToken)
|
|
21402
|
+
this.accessToken = newToken;
|
|
21403
|
+
this.reconnectAttempts = 0;
|
|
21404
|
+
if (this.ws) {
|
|
21405
|
+
this.ws.close();
|
|
21406
|
+
this.ws = null;
|
|
21407
|
+
}
|
|
21408
|
+
this.state = "DISCONNECTED";
|
|
21409
|
+
this.connect();
|
|
21410
|
+
}
|
|
21378
21411
|
subscribe(tokens, callback, symbolMap) {
|
|
21379
21412
|
const sub = {
|
|
21380
21413
|
id: nextSubId3(),
|
|
@@ -21385,7 +21418,11 @@ class FinvasiaSocket {
|
|
|
21385
21418
|
for (const t of tokens) {
|
|
21386
21419
|
this.subscribedTokens.add(t);
|
|
21387
21420
|
if (symbolMap?.has(t)) {
|
|
21388
|
-
|
|
21421
|
+
const sym = symbolMap.get(t);
|
|
21422
|
+
this.tokenToSymbol.set(t, sym);
|
|
21423
|
+
const sep = t.indexOf("|");
|
|
21424
|
+
if (sep !== -1)
|
|
21425
|
+
this.tokenToSymbol.set(t.slice(sep + 1), sym);
|
|
21389
21426
|
}
|
|
21390
21427
|
}
|
|
21391
21428
|
if (this.ws && this.state === "CONNECTED") {
|
|
@@ -21418,6 +21455,10 @@ class FinvasiaSocket {
|
|
|
21418
21455
|
setSymbolMap(map) {
|
|
21419
21456
|
for (const [token, symbol] of map) {
|
|
21420
21457
|
this.tokenToSymbol.set(token, symbol);
|
|
21458
|
+
const sep = token.indexOf("|");
|
|
21459
|
+
if (sep !== -1) {
|
|
21460
|
+
this.tokenToSymbol.set(token.slice(sep + 1), symbol);
|
|
21461
|
+
}
|
|
21421
21462
|
}
|
|
21422
21463
|
}
|
|
21423
21464
|
handleMessage(data) {
|
|
@@ -21429,14 +21470,26 @@ class FinvasiaSocket {
|
|
|
21429
21470
|
if (this.subscribedTokens.size > 0) {
|
|
21430
21471
|
this.sendSubscribe(Array.from(this.subscribedTokens));
|
|
21431
21472
|
}
|
|
21473
|
+
} else {
|
|
21474
|
+
console.error(`[FinvasiaSocket] Auth rejected (ck NOT_OK) \u2014 token invalid, stopping reconnect`);
|
|
21475
|
+
this.reconnectAttempts = this.maxReconnects;
|
|
21476
|
+
this.state = "DISCONNECTED";
|
|
21477
|
+
this.onAuthFailed?.();
|
|
21432
21478
|
}
|
|
21433
21479
|
return;
|
|
21434
21480
|
}
|
|
21435
21481
|
if (type === "tf" || type === "tk" || type === "dk" || type === "df") {
|
|
21436
21482
|
const tick = data;
|
|
21437
|
-
const
|
|
21438
|
-
const
|
|
21483
|
+
const rawToken = tick.tk;
|
|
21484
|
+
const exch = data["e"] ?? "";
|
|
21485
|
+
const fullKey = exch ? `${exch}|${rawToken}` : rawToken;
|
|
21486
|
+
if (tick.ts && !this.tokenToSymbol.has(fullKey)) {
|
|
21487
|
+
this.tokenToSymbol.set(fullKey, tick.ts);
|
|
21488
|
+
this.tokenToSymbol.set(rawToken, tick.ts);
|
|
21489
|
+
}
|
|
21490
|
+
const symbol = this.tokenToSymbol.get(fullKey) ?? this.tokenToSymbol.get(rawToken);
|
|
21439
21491
|
const unified = fromTick2(tick, symbol);
|
|
21492
|
+
unified.token = fullKey;
|
|
21440
21493
|
this.dispatchTick(unified);
|
|
21441
21494
|
return;
|
|
21442
21495
|
}
|
|
@@ -21487,6 +21540,27 @@ class FinvasiaSocket {
|
|
|
21487
21540
|
}
|
|
21488
21541
|
|
|
21489
21542
|
// src/brokers/finvasia/finvasia-instruments.ts
|
|
21543
|
+
import { inflateRawSync } from "zlib";
|
|
21544
|
+
function extractTextFromZip(buffer) {
|
|
21545
|
+
const bytes = new Uint8Array(buffer);
|
|
21546
|
+
if (bytes.length < 30 || bytes[0] !== 80 || bytes[1] !== 75 || bytes[2] !== 3 || bytes[3] !== 4) {
|
|
21547
|
+
return new TextDecoder().decode(buffer);
|
|
21548
|
+
}
|
|
21549
|
+
const view = new DataView(buffer);
|
|
21550
|
+
const compressionMethod = view.getUint16(8, true);
|
|
21551
|
+
const compressedSize = view.getUint32(18, true);
|
|
21552
|
+
const filenameLength = view.getUint16(26, true);
|
|
21553
|
+
const extraFieldLength = view.getUint16(28, true);
|
|
21554
|
+
const dataOffset = 30 + filenameLength + extraFieldLength;
|
|
21555
|
+
const compressedData = new Uint8Array(buffer, dataOffset, compressedSize);
|
|
21556
|
+
if (compressionMethod === 0) {
|
|
21557
|
+
return new TextDecoder().decode(compressedData);
|
|
21558
|
+
}
|
|
21559
|
+
if (compressionMethod === 8) {
|
|
21560
|
+
return new TextDecoder().decode(inflateRawSync(compressedData));
|
|
21561
|
+
}
|
|
21562
|
+
throw new Error(`Unsupported ZIP compression method: ${compressionMethod}`);
|
|
21563
|
+
}
|
|
21490
21564
|
var COL2 = {
|
|
21491
21565
|
EXCHANGE: 0,
|
|
21492
21566
|
TOKEN: 1,
|
|
@@ -21499,6 +21573,18 @@ var COL2 = {
|
|
|
21499
21573
|
STRIKE: 8,
|
|
21500
21574
|
OPTION_TYPE: 9
|
|
21501
21575
|
};
|
|
21576
|
+
var HEADER_MAP = {
|
|
21577
|
+
Exchange: "exchange",
|
|
21578
|
+
Token: "token",
|
|
21579
|
+
LotSize: "lot_size",
|
|
21580
|
+
Symbol: "symbol",
|
|
21581
|
+
TradingSymbol: "trading_symbol",
|
|
21582
|
+
Expiry: "expiry",
|
|
21583
|
+
Instrument: "instrument_type",
|
|
21584
|
+
OptionType: "option_type",
|
|
21585
|
+
StrikePrice: "strike",
|
|
21586
|
+
TickSize: "tick_size"
|
|
21587
|
+
};
|
|
21502
21588
|
var EXCHANGE_MAP2 = {
|
|
21503
21589
|
NSE: "NSE",
|
|
21504
21590
|
BSE: "BSE",
|
|
@@ -21558,7 +21644,7 @@ class FinvasiaInstruments {
|
|
|
21558
21644
|
const response = await fetch(url);
|
|
21559
21645
|
if (!response.ok)
|
|
21560
21646
|
continue;
|
|
21561
|
-
const text = await response.
|
|
21647
|
+
const text = extractTextFromZip(await response.arrayBuffer());
|
|
21562
21648
|
const lines = text.split(`
|
|
21563
21649
|
`);
|
|
21564
21650
|
let headers = null;
|
|
@@ -21566,10 +21652,10 @@ class FinvasiaInstruments {
|
|
|
21566
21652
|
const trimmed = line.trim();
|
|
21567
21653
|
if (!trimmed)
|
|
21568
21654
|
continue;
|
|
21569
|
-
const fields = trimmed.split("
|
|
21655
|
+
const fields = trimmed.split(",");
|
|
21570
21656
|
if (!headers) {
|
|
21571
21657
|
if (fields[0] === "Exchange" || fields[0] === "exch") {
|
|
21572
|
-
headers = fields;
|
|
21658
|
+
headers = fields.map((h) => HEADER_MAP[h] ?? h.toLowerCase());
|
|
21573
21659
|
continue;
|
|
21574
21660
|
}
|
|
21575
21661
|
headers = [];
|
|
@@ -21685,6 +21771,7 @@ class FinvasiaBroker {
|
|
|
21685
21771
|
};
|
|
21686
21772
|
session = null;
|
|
21687
21773
|
socket = null;
|
|
21774
|
+
credentials = null;
|
|
21688
21775
|
instrumentsImpl;
|
|
21689
21776
|
_iMaster;
|
|
21690
21777
|
constructor(options) {
|
|
@@ -21696,6 +21783,7 @@ class FinvasiaBroker {
|
|
|
21696
21783
|
const result = await authenticate2(creds);
|
|
21697
21784
|
if (!result.ok)
|
|
21698
21785
|
return result;
|
|
21786
|
+
this.credentials = creds;
|
|
21699
21787
|
this.session = result.value;
|
|
21700
21788
|
this.instrumentsImpl.setCredentials(this.session.userId, this.session.accessToken);
|
|
21701
21789
|
return result;
|
|
@@ -21756,20 +21844,28 @@ class FinvasiaBroker {
|
|
|
21756
21844
|
async modifyOrder(orderId, params) {
|
|
21757
21845
|
try {
|
|
21758
21846
|
const uid = this.session?.userId ?? "";
|
|
21759
|
-
const
|
|
21847
|
+
const histRaw = await this.post("SingleOrdHist", {
|
|
21760
21848
|
uid,
|
|
21761
21849
|
norenordno: orderId
|
|
21850
|
+
});
|
|
21851
|
+
if (!Array.isArray(histRaw) || histRaw.length === 0) {
|
|
21852
|
+
return Err(new Error(`Cannot modify order ${orderId}: order not found`));
|
|
21853
|
+
}
|
|
21854
|
+
const latestOrder = histRaw[histRaw.length - 1];
|
|
21855
|
+
const payload = {
|
|
21856
|
+
uid,
|
|
21857
|
+
norenordno: orderId,
|
|
21858
|
+
exch: latestOrder.exch,
|
|
21859
|
+
tsym: latestOrder.tsym,
|
|
21860
|
+
prctyp: latestOrder.prctyp,
|
|
21861
|
+
prc: String(params.price ?? latestOrder.prc),
|
|
21862
|
+
qty: String(params.quantity ?? latestOrder.qty),
|
|
21863
|
+
ret: params.validity ?? latestOrder.ret
|
|
21762
21864
|
};
|
|
21763
|
-
if (params.price !== undefined)
|
|
21764
|
-
payload["prc"] = String(params.price);
|
|
21765
|
-
if (params.quantity !== undefined)
|
|
21766
|
-
payload["qty"] = String(params.quantity);
|
|
21767
21865
|
if (params.triggerPrice !== undefined)
|
|
21768
21866
|
payload["trgprc"] = String(params.triggerPrice);
|
|
21769
21867
|
if (params.type !== undefined)
|
|
21770
21868
|
payload["prctyp"] = params.type;
|
|
21771
|
-
if (params.validity !== undefined)
|
|
21772
|
-
payload["ret"] = params.validity;
|
|
21773
21869
|
const res = await this.post("ModifyOrder", payload);
|
|
21774
21870
|
if (res.stat !== "Ok") {
|
|
21775
21871
|
return Err(new Error(`Order modification failed: ${res.emsg ?? "Unknown error"}`));
|
|
@@ -21839,7 +21935,7 @@ class FinvasiaBroker {
|
|
|
21839
21935
|
async getPositions() {
|
|
21840
21936
|
try {
|
|
21841
21937
|
const uid = this.session?.userId ?? "";
|
|
21842
|
-
const raw = await this.post("PositionBook", { uid });
|
|
21938
|
+
const raw = await this.post("PositionBook", { uid, actid: uid });
|
|
21843
21939
|
if (!Array.isArray(raw))
|
|
21844
21940
|
return Ok([]);
|
|
21845
21941
|
return Ok(raw.map(fromPosition2));
|
|
@@ -21984,6 +22080,10 @@ class FinvasiaBroker {
|
|
|
21984
22080
|
this.ensureSocket();
|
|
21985
22081
|
return this.socket.subscribe(tokens, cb);
|
|
21986
22082
|
}
|
|
22083
|
+
setSymbolMap(map) {
|
|
22084
|
+
this.ensureSocket();
|
|
22085
|
+
this.socket.setSymbolMap(map);
|
|
22086
|
+
}
|
|
21987
22087
|
unsubscribeTicks(sub) {
|
|
21988
22088
|
if (this.socket) {
|
|
21989
22089
|
this.socket.unsubscribe(sub);
|
|
@@ -22091,9 +22191,30 @@ class FinvasiaBroker {
|
|
|
22091
22191
|
return;
|
|
22092
22192
|
if (!this.session)
|
|
22093
22193
|
throw new Error("Cannot create socket without an active session");
|
|
22094
|
-
this.socket = new FinvasiaSocket(this.session.userId, this.session.accessToken)
|
|
22194
|
+
this.socket = new FinvasiaSocket(this.session.userId, this.session.accessToken, () => {
|
|
22195
|
+
this.handleSocketAuthFailed();
|
|
22196
|
+
});
|
|
22095
22197
|
this.socket.connect();
|
|
22096
22198
|
}
|
|
22199
|
+
handleSocketAuthFailed() {
|
|
22200
|
+
if (!this.credentials) {
|
|
22201
|
+
console.error("[FinvasiaBroker] Socket auth failed but no credentials stored \u2014 cannot re-authenticate");
|
|
22202
|
+
return;
|
|
22203
|
+
}
|
|
22204
|
+
console.log("[FinvasiaBroker] Socket auth failed, re-authenticating...");
|
|
22205
|
+
authenticate2(this.credentials).then((result) => {
|
|
22206
|
+
if (!result.ok) {
|
|
22207
|
+
console.error("[FinvasiaBroker] Re-authentication failed:", result.error instanceof Error ? result.error.message : result.error);
|
|
22208
|
+
return;
|
|
22209
|
+
}
|
|
22210
|
+
this.session = result.value;
|
|
22211
|
+
this.instrumentsImpl.setCredentials(this.session.userId, this.session.accessToken);
|
|
22212
|
+
console.log("[FinvasiaBroker] Re-authenticated successfully, reconnecting socket");
|
|
22213
|
+
this.socket?.reconnect(this.session.accessToken);
|
|
22214
|
+
}).catch((err) => {
|
|
22215
|
+
console.error("[FinvasiaBroker] Re-authentication exception:", err instanceof Error ? err.message : err);
|
|
22216
|
+
});
|
|
22217
|
+
}
|
|
22097
22218
|
}
|
|
22098
22219
|
// src/brokers/dhan/dhan-constants.ts
|
|
22099
22220
|
var EXCHANGE_TO_DHAN = {
|
|
@@ -22113,7 +22234,7 @@ var EXCHANGE_FROM_DHAN = {
|
|
|
22113
22234
|
NSE_CURRENCY: "CDS"
|
|
22114
22235
|
};
|
|
22115
22236
|
var PRODUCT_TO_DHAN = {
|
|
22116
|
-
INTRADAY: "
|
|
22237
|
+
INTRADAY: "INTRADAY",
|
|
22117
22238
|
DELIVERY: "CNC",
|
|
22118
22239
|
NORMAL: "MARGIN"
|
|
22119
22240
|
};
|
|
@@ -22163,6 +22284,7 @@ var VALIDITY_FROM_DHAN = {
|
|
|
22163
22284
|
IOC: "IOC"
|
|
22164
22285
|
};
|
|
22165
22286
|
var DHAN_API_BASE = "https://api.dhan.co/v2";
|
|
22287
|
+
var DHAN_AUTH_BASE = "https://auth.dhan.co";
|
|
22166
22288
|
var DHAN_WS_URL = "wss://api-feed.dhan.co";
|
|
22167
22289
|
var DHAN_INSTRUMENTS_URL = "https://images.dhan.co/api-data/api-scrip-master.csv";
|
|
22168
22290
|
var CANDLE_INTERVAL_TO_DHAN = {
|
|
@@ -22208,6 +22330,79 @@ async function authenticate3(creds) {
|
|
|
22208
22330
|
return Err(err instanceof Error ? err : new Error(String(err)));
|
|
22209
22331
|
}
|
|
22210
22332
|
}
|
|
22333
|
+
async function generateConsent(creds) {
|
|
22334
|
+
const { apiKey: appId, apiSecret: appSecret, userId: clientId } = creds;
|
|
22335
|
+
if (!appId)
|
|
22336
|
+
return Err(new Error("Missing apiKey (App ID) in credentials"));
|
|
22337
|
+
if (!appSecret)
|
|
22338
|
+
return Err(new Error("Missing apiSecret (App Secret) in credentials"));
|
|
22339
|
+
if (!clientId)
|
|
22340
|
+
return Err(new Error("Missing userId (Dhan Client ID) in credentials"));
|
|
22341
|
+
try {
|
|
22342
|
+
const response = await fetch(`${DHAN_AUTH_BASE}/app/generate-consent?client_id=${clientId}`, {
|
|
22343
|
+
method: "POST",
|
|
22344
|
+
headers: {
|
|
22345
|
+
"Content-Type": "application/json",
|
|
22346
|
+
app_id: appId,
|
|
22347
|
+
app_secret: appSecret
|
|
22348
|
+
},
|
|
22349
|
+
body: JSON.stringify({})
|
|
22350
|
+
});
|
|
22351
|
+
if (!response.ok) {
|
|
22352
|
+
const body = await response.text();
|
|
22353
|
+
return Err(new Error(`Dhan generate-consent failed (${response.status}): ${body}`));
|
|
22354
|
+
}
|
|
22355
|
+
const data = await response.json();
|
|
22356
|
+
if (!data.consentAppId) {
|
|
22357
|
+
return Err(new Error(data.message ?? "Failed to generate consent \u2014 no consentAppId returned"));
|
|
22358
|
+
}
|
|
22359
|
+
const loginUrl = `${DHAN_AUTH_BASE}/login/consentApp-login?consentAppId=${data.consentAppId}`;
|
|
22360
|
+
return Ok({ consentAppId: data.consentAppId, loginUrl });
|
|
22361
|
+
} catch (err) {
|
|
22362
|
+
return Err(err instanceof Error ? err : new Error(String(err)));
|
|
22363
|
+
}
|
|
22364
|
+
}
|
|
22365
|
+
async function consumeConsent(creds, tokenId) {
|
|
22366
|
+
const { apiKey: appId, apiSecret: appSecret, userId: clientId } = creds;
|
|
22367
|
+
if (!appId)
|
|
22368
|
+
return Err(new Error("Missing apiKey (App ID) in credentials"));
|
|
22369
|
+
if (!appSecret)
|
|
22370
|
+
return Err(new Error("Missing apiSecret (App Secret) in credentials"));
|
|
22371
|
+
if (!tokenId)
|
|
22372
|
+
return Err(new Error("Missing tokenId from callback"));
|
|
22373
|
+
try {
|
|
22374
|
+
const response = await fetch(`${DHAN_AUTH_BASE}/app/consumeApp-consent?tokenId=${encodeURIComponent(tokenId)}`, {
|
|
22375
|
+
method: "POST",
|
|
22376
|
+
headers: {
|
|
22377
|
+
"Content-Type": "application/json",
|
|
22378
|
+
app_id: appId,
|
|
22379
|
+
app_secret: appSecret
|
|
22380
|
+
},
|
|
22381
|
+
body: JSON.stringify({})
|
|
22382
|
+
});
|
|
22383
|
+
if (!response.ok) {
|
|
22384
|
+
const body = await response.text();
|
|
22385
|
+
return Err(new Error(`Dhan consumeApp-consent failed (${response.status}): ${body}`));
|
|
22386
|
+
}
|
|
22387
|
+
const data = await response.json();
|
|
22388
|
+
if (!data.accessToken) {
|
|
22389
|
+
return Err(new Error(data.message ?? "Failed to get access token \u2014 no accessToken returned"));
|
|
22390
|
+
}
|
|
22391
|
+
const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000;
|
|
22392
|
+
const session = {
|
|
22393
|
+
accessToken: data.accessToken,
|
|
22394
|
+
refreshToken: undefined,
|
|
22395
|
+
expiresAt: Date.now() + thirtyDaysMs,
|
|
22396
|
+
ttlSeconds: 30 * 24 * 60 * 60,
|
|
22397
|
+
brokerId: "dhan",
|
|
22398
|
+
userId: clientId ?? "",
|
|
22399
|
+
metadata: { authMode: "oauth" }
|
|
22400
|
+
};
|
|
22401
|
+
return Ok(session);
|
|
22402
|
+
} catch (err) {
|
|
22403
|
+
return Err(err instanceof Error ? err : new Error(String(err)));
|
|
22404
|
+
}
|
|
22405
|
+
}
|
|
22211
22406
|
async function refreshSession3(_session) {
|
|
22212
22407
|
return Err(new Error("Dhan does not support token refresh. Generate a new access token from the Dhan developer portal."));
|
|
22213
22408
|
}
|
|
@@ -22238,10 +22433,10 @@ function safeSide3(raw) {
|
|
|
22238
22433
|
}
|
|
22239
22434
|
function parseTimestamp3(ts) {
|
|
22240
22435
|
if (!ts)
|
|
22241
|
-
return
|
|
22436
|
+
return 0;
|
|
22242
22437
|
if (typeof ts === "number")
|
|
22243
22438
|
return ts > 1000000000000 ? ts : ts * 1000;
|
|
22244
|
-
return new Date(ts).getTime() ||
|
|
22439
|
+
return new Date(ts).getTime() || 0;
|
|
22245
22440
|
}
|
|
22246
22441
|
function inferInstrumentType3(exchangeSegment, tradingSymbol) {
|
|
22247
22442
|
if (exchangeSegment === "NSE_EQ" || exchangeSegment === "BSE_EQ")
|
|
@@ -22350,6 +22545,7 @@ function fromTick3(tick, symbolLookup) {
|
|
|
22350
22545
|
function fromHolding3(holding, ltp = 0) {
|
|
22351
22546
|
const qty = holding.totalQty ?? holding.availableQty;
|
|
22352
22547
|
const avgPrice = holding.avgCostPrice;
|
|
22548
|
+
ltp = holding.lastTradedPrice ?? ltp;
|
|
22353
22549
|
const pnl = (ltp - avgPrice) * qty;
|
|
22354
22550
|
return {
|
|
22355
22551
|
broker: "dhan",
|
|
@@ -22391,11 +22587,29 @@ function fromQuote3(quote, symbol, exchange) {
|
|
|
22391
22587
|
}
|
|
22392
22588
|
|
|
22393
22589
|
// src/brokers/dhan/dhan-socket.ts
|
|
22590
|
+
import WS2 from "ws";
|
|
22394
22591
|
var subIdCounter4 = 0;
|
|
22395
22592
|
function nextSubId4() {
|
|
22396
22593
|
subIdCounter4 += 1;
|
|
22397
22594
|
return `dsub_${subIdCounter4}`;
|
|
22398
22595
|
}
|
|
22596
|
+
var EXCH_SEG_FROM_BYTE = {
|
|
22597
|
+
0: "IDX_I",
|
|
22598
|
+
1: "NSE_EQ",
|
|
22599
|
+
2: "NSE_FNO",
|
|
22600
|
+
3: "NSE_CURRENCY",
|
|
22601
|
+
4: "BSE_EQ",
|
|
22602
|
+
5: "MCX_COMM",
|
|
22603
|
+
7: "BSE_CURRENCY",
|
|
22604
|
+
8: "BSE_FNO"
|
|
22605
|
+
};
|
|
22606
|
+
function splitToken(token) {
|
|
22607
|
+
const sep = token.includes("|") ? "|" : ":";
|
|
22608
|
+
const idx = token.indexOf(sep);
|
|
22609
|
+
if (idx === -1)
|
|
22610
|
+
return ["NSE_EQ", token];
|
|
22611
|
+
return [token.slice(0, idx), token.slice(idx + 1)];
|
|
22612
|
+
}
|
|
22399
22613
|
|
|
22400
22614
|
class DhanSocket {
|
|
22401
22615
|
ws = null;
|
|
@@ -22419,36 +22633,36 @@ class DhanSocket {
|
|
|
22419
22633
|
this.state = "CONNECTING";
|
|
22420
22634
|
try {
|
|
22421
22635
|
const url = `${DHAN_WS_URL}?version=2&token=${this.accessToken}&clientId=${this.clientId}&authType=2`;
|
|
22422
|
-
this.ws = new
|
|
22423
|
-
this.ws.binaryType = "arraybuffer";
|
|
22636
|
+
this.ws = new WS2(url);
|
|
22424
22637
|
} catch {
|
|
22425
22638
|
this.state = "DISCONNECTED";
|
|
22426
22639
|
return;
|
|
22427
22640
|
}
|
|
22428
|
-
this.ws.
|
|
22641
|
+
this.ws.on("open", () => {
|
|
22429
22642
|
this.state = "CONNECTED";
|
|
22430
22643
|
this.reconnectAttempts = 0;
|
|
22431
22644
|
this.startHeartbeat();
|
|
22432
22645
|
if (this.subscribedTokens.size > 0) {
|
|
22433
22646
|
this.sendSubscribe(Array.from(this.subscribedTokens));
|
|
22434
22647
|
}
|
|
22435
|
-
};
|
|
22436
|
-
this.ws.
|
|
22648
|
+
});
|
|
22649
|
+
this.ws.on("message", (raw, isBinary) => {
|
|
22437
22650
|
try {
|
|
22438
|
-
if (
|
|
22439
|
-
|
|
22651
|
+
if (isBinary) {
|
|
22652
|
+
const buf = raw instanceof ArrayBuffer ? Buffer.from(raw) : Buffer.isBuffer(raw) ? raw : Array.isArray(raw) ? Buffer.concat(raw) : Buffer.from(raw);
|
|
22653
|
+
this.handleBinaryMessage(buf);
|
|
22440
22654
|
} else {
|
|
22441
|
-
const data = JSON.parse(
|
|
22655
|
+
const data = JSON.parse(raw.toString());
|
|
22442
22656
|
this.handleJsonMessage(data);
|
|
22443
22657
|
}
|
|
22444
22658
|
} catch {}
|
|
22445
|
-
};
|
|
22446
|
-
this.ws.
|
|
22659
|
+
});
|
|
22660
|
+
this.ws.on("close", () => {
|
|
22447
22661
|
this.state = "DISCONNECTED";
|
|
22448
22662
|
this.stopHeartbeat();
|
|
22449
22663
|
this.attemptReconnect();
|
|
22450
|
-
};
|
|
22451
|
-
this.ws.
|
|
22664
|
+
});
|
|
22665
|
+
this.ws.on("error", () => {});
|
|
22452
22666
|
}
|
|
22453
22667
|
disconnect() {
|
|
22454
22668
|
this.clearReconnectTimer();
|
|
@@ -22506,39 +22720,100 @@ class DhanSocket {
|
|
|
22506
22720
|
this.tokenToSymbol.set(token, symbol);
|
|
22507
22721
|
}
|
|
22508
22722
|
}
|
|
22509
|
-
handleBinaryMessage(
|
|
22510
|
-
|
|
22511
|
-
if (buffer.byteLength < 48)
|
|
22723
|
+
handleBinaryMessage(data) {
|
|
22724
|
+
if (data.length < 8)
|
|
22512
22725
|
return;
|
|
22513
|
-
|
|
22726
|
+
const responseCode = data.readUInt8(0);
|
|
22727
|
+
const exchSegByte = data.readUInt8(3);
|
|
22728
|
+
const securityId = data.readInt32LE(4);
|
|
22729
|
+
const exchangeSegment = EXCH_SEG_FROM_BYTE[exchSegByte] ?? "NSE_EQ";
|
|
22730
|
+
const fullKey = `${exchangeSegment}:${securityId}`;
|
|
22731
|
+
if (responseCode === 1 && data.length >= 32) {
|
|
22732
|
+
const ltp = data.readFloatLE(8);
|
|
22733
|
+
const ltt = data.readInt32LE(12);
|
|
22734
|
+
const open = data.readFloatLE(16);
|
|
22735
|
+
const close = data.readFloatLE(20);
|
|
22736
|
+
const high = data.readFloatLE(24);
|
|
22737
|
+
const low = data.readFloatLE(28);
|
|
22514
22738
|
const tick = {
|
|
22515
|
-
securityId
|
|
22516
|
-
LTP:
|
|
22517
|
-
open
|
|
22518
|
-
high
|
|
22519
|
-
low
|
|
22520
|
-
close
|
|
22521
|
-
volume:
|
|
22522
|
-
|
|
22523
|
-
|
|
22524
|
-
|
|
22525
|
-
|
|
22526
|
-
|
|
22527
|
-
|
|
22528
|
-
timestamp: Date.now()
|
|
22739
|
+
securityId,
|
|
22740
|
+
LTP: ltp,
|
|
22741
|
+
open,
|
|
22742
|
+
high,
|
|
22743
|
+
low,
|
|
22744
|
+
close,
|
|
22745
|
+
volume: 0,
|
|
22746
|
+
bestBidPrice: 0,
|
|
22747
|
+
bestAskPrice: 0,
|
|
22748
|
+
bestBidQty: 0,
|
|
22749
|
+
bestAskQty: 0,
|
|
22750
|
+
exchangeSegment,
|
|
22751
|
+
timestamp: ltt > 0 ? ltt * 1000 : Date.now()
|
|
22529
22752
|
};
|
|
22530
|
-
const
|
|
22531
|
-
const symbol = this.tokenToSymbol.get(tokenStr);
|
|
22753
|
+
const symbol = this.tokenToSymbol.get(fullKey) ?? this.tokenToSymbol.get(String(securityId));
|
|
22532
22754
|
const unified = fromTick3(tick, symbol);
|
|
22755
|
+
unified.token = fullKey;
|
|
22533
22756
|
this.dispatchTick(unified);
|
|
22534
|
-
}
|
|
22757
|
+
} else if (responseCode === 2 && data.length >= 16) {
|
|
22758
|
+
const ltp = data.readFloatLE(8);
|
|
22759
|
+
const ltt = data.readInt32LE(12);
|
|
22760
|
+
const tick = {
|
|
22761
|
+
securityId,
|
|
22762
|
+
LTP: ltp,
|
|
22763
|
+
open: 0,
|
|
22764
|
+
high: 0,
|
|
22765
|
+
low: 0,
|
|
22766
|
+
close: 0,
|
|
22767
|
+
volume: 0,
|
|
22768
|
+
bestBidPrice: 0,
|
|
22769
|
+
bestAskPrice: 0,
|
|
22770
|
+
bestBidQty: 0,
|
|
22771
|
+
bestAskQty: 0,
|
|
22772
|
+
exchangeSegment,
|
|
22773
|
+
timestamp: ltt > 0 ? ltt * 1000 : Date.now()
|
|
22774
|
+
};
|
|
22775
|
+
const symbol = this.tokenToSymbol.get(fullKey) ?? this.tokenToSymbol.get(String(securityId));
|
|
22776
|
+
const unified = fromTick3(tick, symbol);
|
|
22777
|
+
unified.token = fullKey;
|
|
22778
|
+
this.dispatchTick(unified);
|
|
22779
|
+
} else if (responseCode === 4 && data.length >= 50) {
|
|
22780
|
+
const ltp = data.readFloatLE(8);
|
|
22781
|
+
const ltt = data.readInt32LE(14);
|
|
22782
|
+
const volume = data.readInt32LE(22);
|
|
22783
|
+
const totalSellQty = data.readInt32LE(26);
|
|
22784
|
+
const totalBuyQty = data.readInt32LE(30);
|
|
22785
|
+
const open = data.readFloatLE(34);
|
|
22786
|
+
const close = data.readFloatLE(38);
|
|
22787
|
+
const high = data.readFloatLE(42);
|
|
22788
|
+
const low = data.readFloatLE(46);
|
|
22789
|
+
const tick = {
|
|
22790
|
+
securityId,
|
|
22791
|
+
LTP: ltp,
|
|
22792
|
+
open,
|
|
22793
|
+
high,
|
|
22794
|
+
low,
|
|
22795
|
+
close,
|
|
22796
|
+
volume,
|
|
22797
|
+
bestBidPrice: 0,
|
|
22798
|
+
bestAskPrice: 0,
|
|
22799
|
+
bestBidQty: totalBuyQty,
|
|
22800
|
+
bestAskQty: totalSellQty,
|
|
22801
|
+
exchangeSegment,
|
|
22802
|
+
timestamp: ltt > 0 ? ltt * 1000 : Date.now()
|
|
22803
|
+
};
|
|
22804
|
+
const symbol = this.tokenToSymbol.get(fullKey) ?? this.tokenToSymbol.get(String(securityId));
|
|
22805
|
+
const unified = fromTick3(tick, symbol);
|
|
22806
|
+
unified.token = fullKey;
|
|
22807
|
+
this.dispatchTick(unified);
|
|
22808
|
+
}
|
|
22535
22809
|
}
|
|
22536
22810
|
handleJsonMessage(data) {
|
|
22537
22811
|
if (data["type"] === "ticker_data") {
|
|
22538
22812
|
const tick = data;
|
|
22539
|
-
const
|
|
22540
|
-
const symbol = this.tokenToSymbol.get(
|
|
22813
|
+
const fullKey = tick.exchangeSegment ? `${tick.exchangeSegment}:${tick.securityId}` : String(tick.securityId);
|
|
22814
|
+
const symbol = this.tokenToSymbol.get(fullKey) ?? this.tokenToSymbol.get(String(tick.securityId));
|
|
22541
22815
|
const unified = fromTick3(tick, symbol);
|
|
22816
|
+
unified.token = fullKey;
|
|
22542
22817
|
this.dispatchTick(unified);
|
|
22543
22818
|
}
|
|
22544
22819
|
}
|
|
@@ -22546,10 +22821,10 @@ class DhanSocket {
|
|
|
22546
22821
|
if (!this.ws || this.state !== "CONNECTED")
|
|
22547
22822
|
return;
|
|
22548
22823
|
const instruments = tokens.map((t) => {
|
|
22549
|
-
const
|
|
22824
|
+
const [segment, id] = splitToken(t);
|
|
22550
22825
|
return {
|
|
22551
|
-
ExchangeSegment:
|
|
22552
|
-
SecurityId:
|
|
22826
|
+
ExchangeSegment: segment,
|
|
22827
|
+
SecurityId: id
|
|
22553
22828
|
};
|
|
22554
22829
|
});
|
|
22555
22830
|
const request = {
|
|
@@ -22563,10 +22838,10 @@ class DhanSocket {
|
|
|
22563
22838
|
if (!this.ws || this.state !== "CONNECTED")
|
|
22564
22839
|
return;
|
|
22565
22840
|
const instruments = tokens.map((t) => {
|
|
22566
|
-
const
|
|
22841
|
+
const [segment, id] = splitToken(t);
|
|
22567
22842
|
return {
|
|
22568
|
-
ExchangeSegment:
|
|
22569
|
-
SecurityId:
|
|
22843
|
+
ExchangeSegment: segment,
|
|
22844
|
+
SecurityId: id
|
|
22570
22845
|
};
|
|
22571
22846
|
});
|
|
22572
22847
|
const request = {
|
|
@@ -22590,7 +22865,7 @@ class DhanSocket {
|
|
|
22590
22865
|
this.heartbeatTimer = setInterval(() => {
|
|
22591
22866
|
if (this.ws && this.state === "CONNECTED") {
|
|
22592
22867
|
try {
|
|
22593
|
-
this.ws.
|
|
22868
|
+
this.ws.ping();
|
|
22594
22869
|
} catch {}
|
|
22595
22870
|
}
|
|
22596
22871
|
}, 30000);
|
|
@@ -22632,6 +22907,15 @@ var DHAN_EXCHANGE_MAP = {
|
|
|
22632
22907
|
NFO: "NFO",
|
|
22633
22908
|
MCX: "MCX"
|
|
22634
22909
|
};
|
|
22910
|
+
function resolveDhanExchange(exchId, segment) {
|
|
22911
|
+
if (segment === "D")
|
|
22912
|
+
return exchId === "BSE" ? "BFO" : "NFO";
|
|
22913
|
+
if (segment === "C")
|
|
22914
|
+
return "CDS";
|
|
22915
|
+
if (segment === "M")
|
|
22916
|
+
return "MCX";
|
|
22917
|
+
return exchId === "BSE" ? "BSE" : "NSE";
|
|
22918
|
+
}
|
|
22635
22919
|
function mapInstrumentType3(instName, optionType) {
|
|
22636
22920
|
const opt = optionType?.toUpperCase();
|
|
22637
22921
|
if (opt === "CE")
|
|
@@ -22773,14 +23057,16 @@ class DhanInstruments {
|
|
|
22773
23057
|
const securityId = fields["SEM_SMST_SECURITY_ID"] ?? fields["securityId"] ?? "";
|
|
22774
23058
|
const tradingSymbol = fields["SEM_TRADING_SYMBOL"] ?? fields["tradingSymbol"] ?? "";
|
|
22775
23059
|
const customSymbol = fields["SEM_CUSTOM_SYMBOL"] ?? fields["customSymbol"] ?? tradingSymbol;
|
|
22776
|
-
const
|
|
23060
|
+
const exchId = fields["SEM_EXM_EXCH_ID"] ?? "";
|
|
23061
|
+
const segment = fields["SEM_SEGMENT"] ?? "";
|
|
23062
|
+
const exchangeSegment = fields["exchangeSegment"] ?? "";
|
|
22777
23063
|
const instNameRaw = fields["SEM_INSTRUMENT_NAME"] ?? fields["instrumentType"] ?? "EQUITY";
|
|
22778
23064
|
const optionType = fields["SEM_OPTION_TYPE"] ?? fields["optionType"] ?? "";
|
|
22779
23065
|
const lotSizeRaw = fields["SEM_LOT_UNITS"] ?? fields["lotSize"] ?? "1";
|
|
22780
23066
|
const tickSizeRaw = fields["SEM_TICK_SIZE"] ?? fields["tickSize"] ?? "0.05";
|
|
22781
23067
|
const expiryRaw = fields["SEM_EXPIRY_DATE"] ?? fields["expiryDate"] ?? "";
|
|
22782
23068
|
const strikeRaw = fields["SEM_STRIKE_PRICE"] ?? fields["strikePrice"] ?? "";
|
|
22783
|
-
const exchange = DHAN_EXCHANGE_MAP[
|
|
23069
|
+
const exchange = exchId && segment ? resolveDhanExchange(exchId, segment) : DHAN_EXCHANGE_MAP[exchangeSegment] ?? EXCHANGE_FROM_DHAN[exchangeSegment] ?? "NSE";
|
|
22784
23070
|
const instrumentType = mapInstrumentType3(instNameRaw, optionType);
|
|
22785
23071
|
return {
|
|
22786
23072
|
tradingSymbol,
|
|
@@ -22837,6 +23123,7 @@ class DhanBroker {
|
|
|
22837
23123
|
session = null;
|
|
22838
23124
|
socket = null;
|
|
22839
23125
|
clientId = "";
|
|
23126
|
+
oauthCreds = null;
|
|
22840
23127
|
instrumentsImpl;
|
|
22841
23128
|
_iMaster;
|
|
22842
23129
|
constructor(options) {
|
|
@@ -22864,6 +23151,24 @@ class DhanBroker {
|
|
|
22864
23151
|
return null;
|
|
22865
23152
|
return new Date(this.session.expiresAt);
|
|
22866
23153
|
}
|
|
23154
|
+
async generateConsent(creds) {
|
|
23155
|
+
this.oauthCreds = creds;
|
|
23156
|
+
this.clientId = creds.userId ?? "";
|
|
23157
|
+
return generateConsent(creds);
|
|
23158
|
+
}
|
|
23159
|
+
async consumeConsent(tokenId) {
|
|
23160
|
+
if (!this.oauthCreds) {
|
|
23161
|
+
return Err(new Error("Call generateConsent() before consumeConsent()"));
|
|
23162
|
+
}
|
|
23163
|
+
const result = await consumeConsent(this.oauthCreds, tokenId);
|
|
23164
|
+
if (!result.ok)
|
|
23165
|
+
return result;
|
|
23166
|
+
this.session = result.value;
|
|
23167
|
+
this.clientId = this.oauthCreds.userId ?? "";
|
|
23168
|
+
this.instrumentsImpl.setCredentials(this.clientId, this.session.accessToken);
|
|
23169
|
+
this.oauthCreds = null;
|
|
23170
|
+
return result;
|
|
23171
|
+
}
|
|
22867
23172
|
async sync(force) {
|
|
22868
23173
|
return this._iMaster.syncBroker(this.id, this.instruments, force);
|
|
22869
23174
|
}
|
|
@@ -22883,9 +23188,11 @@ class DhanBroker {
|
|
|
22883
23188
|
if (!asyncResolved.ok) {
|
|
22884
23189
|
return Err(new Error(`Cannot resolve "${params.tradingSymbol}". Either run broker.sync() first to use unified nsekit symbols, or provide exchange and the broker-native tradingSymbol directly.`));
|
|
22885
23190
|
}
|
|
22886
|
-
|
|
23191
|
+
const nativeId = asyncResolved.value.brokerTokens.dhan?.securityId ?? params.tradingSymbol;
|
|
23192
|
+
resolvedParams = { ...params, exchange: asyncResolved.value.exchange, tradingSymbol: nativeId };
|
|
22887
23193
|
} else {
|
|
22888
|
-
|
|
23194
|
+
const nativeId = resolved.value.brokerTokens.dhan?.securityId ?? params.tradingSymbol;
|
|
23195
|
+
resolvedParams = { ...params, exchange: resolved.value.exchange, tradingSymbol: nativeId };
|
|
22889
23196
|
}
|
|
22890
23197
|
}
|
|
22891
23198
|
const dhanParams = toOrderParams3(resolvedParams, this.clientId);
|
|
@@ -22902,20 +23209,18 @@ class DhanBroker {
|
|
|
22902
23209
|
}
|
|
22903
23210
|
async modifyOrder(orderId, params) {
|
|
22904
23211
|
try {
|
|
23212
|
+
const rawOrder = await this.request("GET", `/orders/${orderId}`);
|
|
23213
|
+
const original = Array.isArray(rawOrder) ? rawOrder[0] : rawOrder;
|
|
22905
23214
|
const payload = {
|
|
22906
23215
|
dhanClientId: this.clientId,
|
|
22907
|
-
orderId
|
|
23216
|
+
orderId,
|
|
23217
|
+
orderType: params.type ? ORDER_TYPE_TO_DHAN[params.type] ?? original.orderType : original.orderType,
|
|
23218
|
+
validity: params.validity ?? original.validity ?? "DAY",
|
|
23219
|
+
quantity: params.quantity ?? original.quantity,
|
|
23220
|
+
price: params.price ?? original.price
|
|
22908
23221
|
};
|
|
22909
|
-
if (params.price !== undefined)
|
|
22910
|
-
payload["price"] = params.price;
|
|
22911
|
-
if (params.quantity !== undefined)
|
|
22912
|
-
payload["quantity"] = params.quantity;
|
|
22913
23222
|
if (params.triggerPrice !== undefined)
|
|
22914
23223
|
payload["triggerPrice"] = params.triggerPrice;
|
|
22915
|
-
if (params.type !== undefined)
|
|
22916
|
-
payload["orderType"] = params.type;
|
|
22917
|
-
if (params.validity !== undefined)
|
|
22918
|
-
payload["validity"] = params.validity;
|
|
22919
23224
|
const res = await this.request("PUT", `/orders/${orderId}`, payload);
|
|
22920
23225
|
return Ok({
|
|
22921
23226
|
orderId: res.orderId ?? orderId,
|
|
@@ -22982,7 +23287,11 @@ class DhanBroker {
|
|
|
22982
23287
|
return Ok([]);
|
|
22983
23288
|
return Ok(raw.map((h) => fromHolding3(h)));
|
|
22984
23289
|
} catch (err) {
|
|
22985
|
-
|
|
23290
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
23291
|
+
if (msg.includes("No holdings available") || msg.includes("DH-1111")) {
|
|
23292
|
+
return Ok([]);
|
|
23293
|
+
}
|
|
23294
|
+
return Err(err instanceof Error ? err : new Error(msg));
|
|
22986
23295
|
}
|
|
22987
23296
|
}
|
|
22988
23297
|
async getLTP(symbols) {
|
|
@@ -23124,6 +23433,10 @@ class DhanBroker {
|
|
|
23124
23433
|
this.ensureSocket();
|
|
23125
23434
|
return this.socket.subscribe(tokens, cb);
|
|
23126
23435
|
}
|
|
23436
|
+
setSymbolMap(map) {
|
|
23437
|
+
this.ensureSocket();
|
|
23438
|
+
this.socket.setSymbolMap(map);
|
|
23439
|
+
}
|
|
23127
23440
|
unsubscribeTicks(sub) {
|
|
23128
23441
|
if (this.socket) {
|
|
23129
23442
|
this.socket.unsubscribe(sub);
|