nsekit 0.2.0 → 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.
Files changed (34) hide show
  1. package/README.md +51 -33
  2. package/dist/brokers/dhan/DhanBroker.d.ts +10 -4
  3. package/dist/brokers/dhan/DhanBroker.d.ts.map +1 -1
  4. package/dist/brokers/dhan/dhan-instruments.d.ts +1 -11
  5. package/dist/brokers/dhan/dhan-instruments.d.ts.map +1 -1
  6. package/dist/brokers/dhan/dhan-socket.d.ts +33 -3
  7. package/dist/brokers/dhan/dhan-socket.d.ts.map +1 -1
  8. package/dist/brokers/finvasia/FinvasiaBroker.d.ts +10 -4
  9. package/dist/brokers/finvasia/FinvasiaBroker.d.ts.map +1 -1
  10. package/dist/brokers/finvasia/finvasia-constants.d.ts +1 -1
  11. package/dist/brokers/finvasia/finvasia-constants.d.ts.map +1 -1
  12. package/dist/brokers/finvasia/finvasia-instruments.d.ts +1 -11
  13. package/dist/brokers/finvasia/finvasia-instruments.d.ts.map +1 -1
  14. package/dist/brokers/finvasia/finvasia-socket.d.ts.map +1 -1
  15. package/dist/brokers/paper/PaperBroker.d.ts +11 -3
  16. package/dist/brokers/paper/PaperBroker.d.ts.map +1 -1
  17. package/dist/brokers/zerodha/ZerodhaBroker.d.ts +10 -4
  18. package/dist/brokers/zerodha/ZerodhaBroker.d.ts.map +1 -1
  19. package/dist/brokers/zerodha/zerodha-instruments.d.ts +0 -12
  20. package/dist/brokers/zerodha/zerodha-instruments.d.ts.map +1 -1
  21. package/dist/index.d.ts +9 -5
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +306 -170
  24. package/dist/instruments/instrument-master.d.ts +9 -2
  25. package/dist/instruments/instrument-master.d.ts.map +1 -1
  26. package/dist/interfaces/broker.interface.d.ts +8 -3
  27. package/dist/interfaces/broker.interface.d.ts.map +1 -1
  28. package/dist/types/broker.d.ts +9 -1
  29. package/dist/types/broker.d.ts.map +1 -1
  30. package/dist/types/index.d.ts +1 -1
  31. package/dist/types/index.d.ts.map +1 -1
  32. package/dist/types/instruments.d.ts +1 -6
  33. package/dist/types/instruments.d.ts.map +1 -1
  34. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -19443,6 +19443,23 @@ class InstrumentMaster {
19443
19443
  const list = matches.slice(0, 5).map((m) => `${m.exchange}:${m.tradingSymbol}`).join(", ");
19444
19444
  return Err(new Error(`Multiple matches found. Specify exchange: ${list}`));
19445
19445
  }
19446
+ async resolveAsync(input, brokerId, brokerInstruments) {
19447
+ const local = this.resolve(input);
19448
+ if (local.ok)
19449
+ return local;
19450
+ if (brokerInstruments.capabilities.searchAPI && brokerInstruments.searchAPI) {
19451
+ const { symbol } = this.parseInput(input);
19452
+ const apiResult = await brokerInstruments.searchAPI(symbol);
19453
+ if (apiResult.ok && apiResult.value.length > 0) {
19454
+ for (const entry of apiResult.value)
19455
+ this.mergeEntry(brokerId, entry);
19456
+ if (this.redis)
19457
+ this.cacheInstrumentsToRedis();
19458
+ return this.resolve(input);
19459
+ }
19460
+ }
19461
+ return Err(new Error(`Symbol "${input}" not found`));
19462
+ }
19446
19463
  async getOptionChainCached(underlying, expiry) {
19447
19464
  const expiryDate = expiry.toISOString().slice(0, 10);
19448
19465
  const cacheKey = `optchain:${underlying.toUpperCase()}:${expiryDate}`;
@@ -20565,33 +20582,6 @@ class ZerodhaInstruments {
20565
20582
  yield row;
20566
20583
  }
20567
20584
  }
20568
- async checkDumpChanged(lastSync) {
20569
- const response = await fetch(KITE_INSTRUMENTS_URL, { method: "HEAD" });
20570
- if (!response.ok)
20571
- return true;
20572
- const etag = response.headers.get("etag");
20573
- const lastModified = response.headers.get("last-modified");
20574
- if (etag && lastSync.etag)
20575
- return etag !== lastSync.etag;
20576
- if (lastModified && lastSync.lastModified)
20577
- return lastModified !== lastSync.lastModified;
20578
- return true;
20579
- }
20580
- async resolve(symbol, exchange) {
20581
- try {
20582
- for await (const raw of this.streamDump()) {
20583
- const fields = raw;
20584
- const rowSymbol = fields["tradingsymbol"] ?? "";
20585
- const rowExchange = fields["exchange"] ?? "";
20586
- if (rowSymbol === symbol && rowExchange === exchange) {
20587
- return Ok(this.normalize(raw));
20588
- }
20589
- }
20590
- return Err(new Error(`Instrument ${symbol} not found on ${exchange} (Zerodha)`));
20591
- } catch (err) {
20592
- return Err(err instanceof Error ? err : new Error(String(err)));
20593
- }
20594
- }
20595
20585
  normalize(raw) {
20596
20586
  const fields = raw;
20597
20587
  const tradingSymbol = fields["tradingsymbol"] ?? fields[COL.TRADINGSYMBOL] ?? "";
@@ -20625,13 +20615,23 @@ class ZerodhaBroker {
20625
20615
  id = "zerodha";
20626
20616
  name = "Zerodha";
20627
20617
  instruments;
20618
+ has = {
20619
+ bulkDump: true,
20620
+ searchAPI: false,
20621
+ optionChain: false,
20622
+ historicalCandles: true,
20623
+ websocket: true,
20624
+ pnlReport: false
20625
+ };
20628
20626
  session = null;
20629
20627
  kite = null;
20630
20628
  socket = null;
20631
20629
  apiKey = "";
20632
20630
  KiteConnect = null;
20633
- constructor() {
20631
+ _iMaster;
20632
+ constructor(options) {
20634
20633
  this.instruments = new ZerodhaInstruments;
20634
+ this._iMaster = new InstrumentMaster(options);
20635
20635
  }
20636
20636
  async authenticate(creds) {
20637
20637
  this.apiKey = creds.apiKey;
@@ -20654,10 +20654,34 @@ class ZerodhaBroker {
20654
20654
  return null;
20655
20655
  return new Date(this.session.expiresAt);
20656
20656
  }
20657
+ async sync(force) {
20658
+ return this._iMaster.syncBroker(this.id, this.instruments, force);
20659
+ }
20660
+ resolve(input) {
20661
+ return this._iMaster.resolve(input);
20662
+ }
20663
+ search(query, exchange, instrumentType, limit) {
20664
+ return this._iMaster.search(query, exchange, instrumentType, limit);
20665
+ }
20657
20666
  async placeOrder(params) {
20658
20667
  try {
20668
+ let resolvedParams = params;
20669
+ if (!params.exchange) {
20670
+ const resolved = this._iMaster.resolve(params.tradingSymbol);
20671
+ if (!resolved.ok) {
20672
+ const asyncResolved = await this._iMaster.resolveAsync(params.tradingSymbol, this.id, this.instruments);
20673
+ if (!asyncResolved.ok) {
20674
+ 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.`));
20675
+ }
20676
+ const nativeSymbol = asyncResolved.value.brokerTokens.zerodha?.tradingsymbol ?? params.tradingSymbol;
20677
+ resolvedParams = { ...params, exchange: asyncResolved.value.exchange, tradingSymbol: nativeSymbol };
20678
+ } else {
20679
+ const nativeSymbol = resolved.value.brokerTokens.zerodha?.tradingsymbol ?? params.tradingSymbol;
20680
+ resolvedParams = { ...params, exchange: resolved.value.exchange, tradingSymbol: nativeSymbol };
20681
+ }
20682
+ }
20659
20683
  const kite = await this.ensureKite();
20660
- const kiteParams = toOrderParams(params);
20684
+ const kiteParams = toOrderParams(resolvedParams);
20661
20685
  const res = await kite.placeOrder("regular", kiteParams);
20662
20686
  return Ok({
20663
20687
  orderId: res.order_id,
@@ -20988,7 +21012,7 @@ var VALIDITY_FROM_SHOONYA = {
20988
21012
  IOC: "IOC"
20989
21013
  };
20990
21014
  var SHOONYA_API_BASE = "https://api.shoonya.com/NorenWClientTP";
20991
- var SHOONYA_WS_URL = "wss://api.shoonya.com/NorenWSTP";
21015
+ var SHOONYA_WS_URL = "wss://api.shoonya.com/NorenWSTP/";
20992
21016
  var SHOONYA_INSTRUMENTS_BASE = "https://api.shoonya.com/";
20993
21017
  var SHOONYA_INSTRUMENT_FILES = {
20994
21018
  NSE: "NSE_symbols.txt.zip",
@@ -21288,6 +21312,7 @@ function fromQuote2(quote) {
21288
21312
  }
21289
21313
 
21290
21314
  // src/brokers/finvasia/finvasia-socket.ts
21315
+ import WS from "ws";
21291
21316
  var subIdCounter3 = 0;
21292
21317
  function nextSubId3() {
21293
21318
  subIdCounter3 += 1;
@@ -21314,12 +21339,13 @@ class FinvasiaSocket {
21314
21339
  return;
21315
21340
  this.state = "CONNECTING";
21316
21341
  try {
21317
- this.ws = new WebSocket(SHOONYA_WS_URL);
21342
+ this.ws = new WS(SHOONYA_WS_URL);
21318
21343
  } catch {
21319
21344
  this.state = "DISCONNECTED";
21320
21345
  return;
21321
21346
  }
21322
- this.ws.onopen = () => {
21347
+ this.ws.on("open", () => {
21348
+ console.log(`[FinvasiaSocket] WS open, sending auth for uid=${this.userId}`);
21323
21349
  const authMsg = JSON.stringify({
21324
21350
  t: "c",
21325
21351
  uid: this.userId,
@@ -21328,18 +21354,23 @@ class FinvasiaSocket {
21328
21354
  source: "API"
21329
21355
  });
21330
21356
  this.ws.send(authMsg);
21331
- };
21332
- this.ws.onmessage = (event) => {
21357
+ });
21358
+ this.ws.on("message", (raw) => {
21333
21359
  try {
21334
- const data = JSON.parse(String(event.data));
21360
+ const text = raw.toString();
21361
+ console.log(`[FinvasiaSocket] WS msg: ${text.slice(0, 200)}`);
21362
+ const data = JSON.parse(text);
21335
21363
  this.handleMessage(data);
21336
21364
  } catch {}
21337
- };
21338
- this.ws.onclose = () => {
21365
+ });
21366
+ this.ws.on("close", (code, reason) => {
21367
+ console.error(`[FinvasiaSocket] WS closed: code=${code}, reason=${reason.toString()}`);
21339
21368
  this.state = "DISCONNECTED";
21340
21369
  this.attemptReconnect();
21341
- };
21342
- this.ws.onerror = () => {};
21370
+ });
21371
+ this.ws.on("error", (err) => {
21372
+ console.error(`[FinvasiaSocket] WS error:`, err.message);
21373
+ });
21343
21374
  }
21344
21375
  disconnect() {
21345
21376
  this.clearReconnectTimer();
@@ -21410,9 +21441,12 @@ class FinvasiaSocket {
21410
21441
  }
21411
21442
  if (type === "tf" || type === "tk" || type === "dk" || type === "df") {
21412
21443
  const tick = data;
21413
- const tokenKey = tick.tk;
21414
- const symbol = this.tokenToSymbol.get(tokenKey);
21444
+ const rawToken = tick.tk;
21445
+ const exch = data["e"] ?? "";
21446
+ const fullKey = exch ? `${exch}|${rawToken}` : rawToken;
21447
+ const symbol = this.tokenToSymbol.get(fullKey) ?? this.tokenToSymbol.get(rawToken);
21415
21448
  const unified = fromTick2(tick, symbol);
21449
+ unified.token = fullKey;
21416
21450
  this.dispatchTick(unified);
21417
21451
  return;
21418
21452
  }
@@ -21576,25 +21610,7 @@ class FinvasiaInstruments {
21576
21610
  }
21577
21611
  }
21578
21612
  }
21579
- async checkDumpChanged(lastSync) {
21580
- const firstFile = Object.values(SHOONYA_INSTRUMENT_FILES)[0];
21581
- const url = `${SHOONYA_INSTRUMENTS_BASE}${firstFile}`;
21582
- try {
21583
- const response = await fetch(url, { method: "HEAD" });
21584
- if (!response.ok)
21585
- return true;
21586
- const etag = response.headers.get("etag");
21587
- const lastModified = response.headers.get("last-modified");
21588
- if (etag && lastSync.etag)
21589
- return etag !== lastSync.etag;
21590
- if (lastModified && lastSync.lastModified)
21591
- return lastModified !== lastSync.lastModified;
21592
- return true;
21593
- } catch {
21594
- return true;
21595
- }
21596
- }
21597
- async search(query, exchange) {
21613
+ async searchAPI(query, exchange) {
21598
21614
  if (!this.accessToken) {
21599
21615
  return Err(new Error("Finvasia search requires authentication. Call setCredentials() first."));
21600
21616
  }
@@ -21634,16 +21650,6 @@ class FinvasiaInstruments {
21634
21650
  return Err(err instanceof Error ? err : new Error(String(err)));
21635
21651
  }
21636
21652
  }
21637
- async resolve(symbol, exchange) {
21638
- const result = await this.search(symbol, exchange);
21639
- if (!result.ok)
21640
- return result;
21641
- const match = result.value.find((e) => e.tradingSymbol === symbol && e.exchange === exchange);
21642
- if (!match) {
21643
- return Err(new Error(`Instrument ${symbol} not found on ${exchange} (Finvasia)`));
21644
- }
21645
- return Ok(match);
21646
- }
21647
21653
  normalize(raw) {
21648
21654
  const fields = raw;
21649
21655
  const exchangeRaw = fields["exchange"] ?? fields["exch"] ?? fields["_exchange"] ?? "NSE";
@@ -21679,12 +21685,22 @@ class FinvasiaBroker {
21679
21685
  id = "finvasia";
21680
21686
  name = "Finvasia (Shoonya)";
21681
21687
  instruments;
21688
+ has = {
21689
+ bulkDump: true,
21690
+ searchAPI: true,
21691
+ optionChain: true,
21692
+ historicalCandles: true,
21693
+ websocket: true,
21694
+ pnlReport: false
21695
+ };
21682
21696
  session = null;
21683
21697
  socket = null;
21684
21698
  instrumentsImpl;
21685
- constructor() {
21699
+ _iMaster;
21700
+ constructor(options) {
21686
21701
  this.instrumentsImpl = new FinvasiaInstruments;
21687
21702
  this.instruments = this.instrumentsImpl;
21703
+ this._iMaster = new InstrumentMaster(options);
21688
21704
  }
21689
21705
  async authenticate(creds) {
21690
21706
  const result = await authenticate2(creds);
@@ -21705,10 +21721,34 @@ class FinvasiaBroker {
21705
21721
  return null;
21706
21722
  return new Date(this.session.expiresAt);
21707
21723
  }
21724
+ async sync(force) {
21725
+ return this._iMaster.syncBroker(this.id, this.instruments, force);
21726
+ }
21727
+ resolve(input) {
21728
+ return this._iMaster.resolve(input);
21729
+ }
21730
+ search(query, exchange, instrumentType, limit) {
21731
+ return this._iMaster.search(query, exchange, instrumentType, limit);
21732
+ }
21708
21733
  async placeOrder(params) {
21709
21734
  try {
21735
+ let resolvedParams = params;
21736
+ if (!params.exchange) {
21737
+ const resolved = this._iMaster.resolve(params.tradingSymbol);
21738
+ if (!resolved.ok) {
21739
+ const asyncResolved = await this._iMaster.resolveAsync(params.tradingSymbol, this.id, this.instruments);
21740
+ if (!asyncResolved.ok) {
21741
+ 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.`));
21742
+ }
21743
+ const nativeSymbol = asyncResolved.value.brokerTokens.finvasia?.tsym ?? params.tradingSymbol;
21744
+ resolvedParams = { ...params, exchange: asyncResolved.value.exchange, tradingSymbol: nativeSymbol };
21745
+ } else {
21746
+ const nativeSymbol = resolved.value.brokerTokens.finvasia?.tsym ?? params.tradingSymbol;
21747
+ resolvedParams = { ...params, exchange: resolved.value.exchange, tradingSymbol: nativeSymbol };
21748
+ }
21749
+ }
21710
21750
  const uid = this.session?.userId ?? "";
21711
- const shoonyaParams = toOrderParams2(params, uid, uid);
21751
+ const shoonyaParams = toOrderParams2(resolvedParams, uid, uid);
21712
21752
  const res = await this.post("PlaceOrder", shoonyaParams);
21713
21753
  if (res.stat !== "Ok" || !res.norenordno) {
21714
21754
  return Err(new Error(`Order placement failed: ${res.emsg ?? "Unknown error"}`));
@@ -21726,20 +21766,28 @@ class FinvasiaBroker {
21726
21766
  async modifyOrder(orderId, params) {
21727
21767
  try {
21728
21768
  const uid = this.session?.userId ?? "";
21729
- const payload = {
21769
+ const histRaw = await this.post("SingleOrdHist", {
21730
21770
  uid,
21731
21771
  norenordno: orderId
21772
+ });
21773
+ if (!Array.isArray(histRaw) || histRaw.length === 0) {
21774
+ return Err(new Error(`Cannot modify order ${orderId}: order not found`));
21775
+ }
21776
+ const latestOrder = histRaw[histRaw.length - 1];
21777
+ const payload = {
21778
+ uid,
21779
+ norenordno: orderId,
21780
+ exch: latestOrder.exch,
21781
+ tsym: latestOrder.tsym,
21782
+ prctyp: latestOrder.prctyp,
21783
+ prc: String(params.price ?? latestOrder.prc),
21784
+ qty: String(params.quantity ?? latestOrder.qty),
21785
+ ret: params.validity ?? latestOrder.ret
21732
21786
  };
21733
- if (params.price !== undefined)
21734
- payload["prc"] = String(params.price);
21735
- if (params.quantity !== undefined)
21736
- payload["qty"] = String(params.quantity);
21737
21787
  if (params.triggerPrice !== undefined)
21738
21788
  payload["trgprc"] = String(params.triggerPrice);
21739
21789
  if (params.type !== undefined)
21740
21790
  payload["prctyp"] = params.type;
21741
- if (params.validity !== undefined)
21742
- payload["ret"] = params.validity;
21743
21791
  const res = await this.post("ModifyOrder", payload);
21744
21792
  if (res.stat !== "Ok") {
21745
21793
  return Err(new Error(`Order modification failed: ${res.emsg ?? "Unknown error"}`));
@@ -21809,7 +21857,7 @@ class FinvasiaBroker {
21809
21857
  async getPositions() {
21810
21858
  try {
21811
21859
  const uid = this.session?.userId ?? "";
21812
- const raw = await this.post("PositionBook", { uid });
21860
+ const raw = await this.post("PositionBook", { uid, actid: uid });
21813
21861
  if (!Array.isArray(raw))
21814
21862
  return Ok([]);
21815
21863
  return Ok(raw.map(fromPosition2));
@@ -22083,7 +22131,7 @@ var EXCHANGE_FROM_DHAN = {
22083
22131
  NSE_CURRENCY: "CDS"
22084
22132
  };
22085
22133
  var PRODUCT_TO_DHAN = {
22086
- INTRADAY: "INTRA",
22134
+ INTRADAY: "INTRADAY",
22087
22135
  DELIVERY: "CNC",
22088
22136
  NORMAL: "MARGIN"
22089
22137
  };
@@ -22361,11 +22409,28 @@ function fromQuote3(quote, symbol, exchange) {
22361
22409
  }
22362
22410
 
22363
22411
  // src/brokers/dhan/dhan-socket.ts
22412
+ import WS2 from "ws";
22364
22413
  var subIdCounter4 = 0;
22365
22414
  function nextSubId4() {
22366
22415
  subIdCounter4 += 1;
22367
22416
  return `dsub_${subIdCounter4}`;
22368
22417
  }
22418
+ var EXCH_SEG_FROM_BYTE = {
22419
+ 0: "IDX",
22420
+ 1: "NSE_EQ",
22421
+ 2: "NSE_FNO",
22422
+ 3: "NSE_CURRENCY",
22423
+ 4: "BSE_EQ",
22424
+ 5: "BSE_FNO",
22425
+ 6: "MCX_COMM"
22426
+ };
22427
+ function splitToken(token) {
22428
+ const sep = token.includes("|") ? "|" : ":";
22429
+ const idx = token.indexOf(sep);
22430
+ if (idx === -1)
22431
+ return ["NSE_EQ", token];
22432
+ return [token.slice(0, idx), token.slice(idx + 1)];
22433
+ }
22369
22434
 
22370
22435
  class DhanSocket {
22371
22436
  ws = null;
@@ -22389,36 +22454,36 @@ class DhanSocket {
22389
22454
  this.state = "CONNECTING";
22390
22455
  try {
22391
22456
  const url = `${DHAN_WS_URL}?version=2&token=${this.accessToken}&clientId=${this.clientId}&authType=2`;
22392
- this.ws = new WebSocket(url);
22393
- this.ws.binaryType = "arraybuffer";
22457
+ this.ws = new WS2(url);
22394
22458
  } catch {
22395
22459
  this.state = "DISCONNECTED";
22396
22460
  return;
22397
22461
  }
22398
- this.ws.onopen = () => {
22462
+ this.ws.on("open", () => {
22399
22463
  this.state = "CONNECTED";
22400
22464
  this.reconnectAttempts = 0;
22401
22465
  this.startHeartbeat();
22402
22466
  if (this.subscribedTokens.size > 0) {
22403
22467
  this.sendSubscribe(Array.from(this.subscribedTokens));
22404
22468
  }
22405
- };
22406
- this.ws.onmessage = (event) => {
22469
+ });
22470
+ this.ws.on("message", (raw, isBinary) => {
22407
22471
  try {
22408
- if (event.data instanceof ArrayBuffer) {
22409
- this.handleBinaryMessage(event.data);
22472
+ if (isBinary) {
22473
+ const buf = raw instanceof ArrayBuffer ? Buffer.from(raw) : Buffer.isBuffer(raw) ? raw : Array.isArray(raw) ? Buffer.concat(raw) : Buffer.from(raw);
22474
+ this.handleBinaryMessage(buf);
22410
22475
  } else {
22411
- const data = JSON.parse(String(event.data));
22476
+ const data = JSON.parse(raw.toString());
22412
22477
  this.handleJsonMessage(data);
22413
22478
  }
22414
22479
  } catch {}
22415
- };
22416
- this.ws.onclose = () => {
22480
+ });
22481
+ this.ws.on("close", () => {
22417
22482
  this.state = "DISCONNECTED";
22418
22483
  this.stopHeartbeat();
22419
22484
  this.attemptReconnect();
22420
- };
22421
- this.ws.onerror = () => {};
22485
+ });
22486
+ this.ws.on("error", () => {});
22422
22487
  }
22423
22488
  disconnect() {
22424
22489
  this.clearReconnectTimer();
@@ -22476,39 +22541,74 @@ class DhanSocket {
22476
22541
  this.tokenToSymbol.set(token, symbol);
22477
22542
  }
22478
22543
  }
22479
- handleBinaryMessage(buffer) {
22480
- const view = new DataView(buffer);
22481
- if (buffer.byteLength < 48)
22544
+ handleBinaryMessage(data) {
22545
+ if (data.length < 8)
22482
22546
  return;
22483
- try {
22547
+ const responseCode = data.readUInt8(0);
22548
+ const exchSegByte = data.readUInt8(3);
22549
+ const securityId = data.readInt32LE(4);
22550
+ const exchangeSegment = EXCH_SEG_FROM_BYTE[exchSegByte] ?? "NSE_EQ";
22551
+ const fullKey = `${exchangeSegment}|${securityId}`;
22552
+ if (responseCode === 2 && data.length >= 16) {
22553
+ const ltp = data.readFloatLE(8);
22554
+ const ltt = data.readInt32LE(12);
22484
22555
  const tick = {
22485
- securityId: view.getInt32(0, true),
22486
- LTP: view.getFloat32(4, true),
22487
- open: view.getFloat32(8, true),
22488
- high: view.getFloat32(12, true),
22489
- low: view.getFloat32(16, true),
22490
- close: view.getFloat32(20, true),
22491
- volume: view.getInt32(24, true),
22492
- OI: view.getInt32(28, true),
22493
- bestBidPrice: view.getFloat32(32, true),
22494
- bestAskPrice: view.getFloat32(36, true),
22495
- bestBidQty: view.getInt32(40, true),
22496
- bestAskQty: view.getInt32(44, true),
22497
- exchangeSegment: "",
22498
- timestamp: Date.now()
22556
+ securityId,
22557
+ LTP: ltp,
22558
+ open: 0,
22559
+ high: 0,
22560
+ low: 0,
22561
+ close: 0,
22562
+ volume: 0,
22563
+ bestBidPrice: 0,
22564
+ bestAskPrice: 0,
22565
+ bestBidQty: 0,
22566
+ bestAskQty: 0,
22567
+ exchangeSegment,
22568
+ timestamp: ltt > 0 ? ltt * 1000 : Date.now()
22499
22569
  };
22500
- const tokenStr = String(tick.securityId);
22501
- const symbol = this.tokenToSymbol.get(tokenStr);
22570
+ const symbol = this.tokenToSymbol.get(fullKey) ?? this.tokenToSymbol.get(String(securityId));
22502
22571
  const unified = fromTick3(tick, symbol);
22572
+ unified.token = fullKey;
22503
22573
  this.dispatchTick(unified);
22504
- } catch {}
22574
+ } else if (responseCode === 4 && data.length >= 50) {
22575
+ const ltp = data.readFloatLE(8);
22576
+ const ltt = data.readInt32LE(14);
22577
+ const volume = data.readInt32LE(22);
22578
+ const totalSellQty = data.readInt32LE(26);
22579
+ const totalBuyQty = data.readInt32LE(30);
22580
+ const open = data.readFloatLE(34);
22581
+ const close = data.readFloatLE(38);
22582
+ const high = data.readFloatLE(42);
22583
+ const low = data.readFloatLE(46);
22584
+ const tick = {
22585
+ securityId,
22586
+ LTP: ltp,
22587
+ open,
22588
+ high,
22589
+ low,
22590
+ close,
22591
+ volume,
22592
+ bestBidPrice: 0,
22593
+ bestAskPrice: 0,
22594
+ bestBidQty: totalBuyQty,
22595
+ bestAskQty: totalSellQty,
22596
+ exchangeSegment,
22597
+ timestamp: ltt > 0 ? ltt * 1000 : Date.now()
22598
+ };
22599
+ const symbol = this.tokenToSymbol.get(fullKey) ?? this.tokenToSymbol.get(String(securityId));
22600
+ const unified = fromTick3(tick, symbol);
22601
+ unified.token = fullKey;
22602
+ this.dispatchTick(unified);
22603
+ }
22505
22604
  }
22506
22605
  handleJsonMessage(data) {
22507
22606
  if (data["type"] === "ticker_data") {
22508
22607
  const tick = data;
22509
- const tokenStr = String(tick.securityId);
22510
- const symbol = this.tokenToSymbol.get(tokenStr);
22608
+ const fullKey = tick.exchangeSegment ? `${tick.exchangeSegment}|${tick.securityId}` : String(tick.securityId);
22609
+ const symbol = this.tokenToSymbol.get(fullKey) ?? this.tokenToSymbol.get(String(tick.securityId));
22511
22610
  const unified = fromTick3(tick, symbol);
22611
+ unified.token = fullKey;
22512
22612
  this.dispatchTick(unified);
22513
22613
  }
22514
22614
  }
@@ -22516,10 +22616,10 @@ class DhanSocket {
22516
22616
  if (!this.ws || this.state !== "CONNECTED")
22517
22617
  return;
22518
22618
  const instruments = tokens.map((t) => {
22519
- const parts = t.split(":");
22619
+ const [segment, id] = splitToken(t);
22520
22620
  return {
22521
- ExchangeSegment: parts[0] ?? "NSE_EQ",
22522
- SecurityId: parts[1] ?? parts[0]
22621
+ ExchangeSegment: segment,
22622
+ SecurityId: id
22523
22623
  };
22524
22624
  });
22525
22625
  const request = {
@@ -22533,10 +22633,10 @@ class DhanSocket {
22533
22633
  if (!this.ws || this.state !== "CONNECTED")
22534
22634
  return;
22535
22635
  const instruments = tokens.map((t) => {
22536
- const parts = t.split(":");
22636
+ const [segment, id] = splitToken(t);
22537
22637
  return {
22538
- ExchangeSegment: parts[0] ?? "NSE_EQ",
22539
- SecurityId: parts[1] ?? parts[0]
22638
+ ExchangeSegment: segment,
22639
+ SecurityId: id
22540
22640
  };
22541
22641
  });
22542
22642
  const request = {
@@ -22560,7 +22660,7 @@ class DhanSocket {
22560
22660
  this.heartbeatTimer = setInterval(() => {
22561
22661
  if (this.ws && this.state === "CONNECTED") {
22562
22662
  try {
22563
- this.ws.send(JSON.stringify({ RequestCode: 12 }));
22663
+ this.ws.ping();
22564
22664
  } catch {}
22565
22665
  }
22566
22666
  }, 30000);
@@ -22695,23 +22795,7 @@ class DhanInstruments {
22695
22795
  yield row;
22696
22796
  }
22697
22797
  }
22698
- async checkDumpChanged(lastSync) {
22699
- try {
22700
- const response = await fetch(DHAN_INSTRUMENTS_URL, { method: "HEAD" });
22701
- if (!response.ok)
22702
- return true;
22703
- const etag = response.headers.get("etag");
22704
- const lastModified = response.headers.get("last-modified");
22705
- if (etag && lastSync.etag)
22706
- return etag !== lastSync.etag;
22707
- if (lastModified && lastSync.lastModified)
22708
- return lastModified !== lastSync.lastModified;
22709
- return true;
22710
- } catch {
22711
- return true;
22712
- }
22713
- }
22714
- async search(query, exchange) {
22798
+ async searchAPI(query, exchange) {
22715
22799
  if (!this.accessToken) {
22716
22800
  return Err(new Error("Dhan search requires authentication. Call setCredentials() first."));
22717
22801
  }
@@ -22754,16 +22838,6 @@ class DhanInstruments {
22754
22838
  return Err(err instanceof Error ? err : new Error(String(err)));
22755
22839
  }
22756
22840
  }
22757
- async resolve(symbol, exchange) {
22758
- const result = await this.search(symbol, exchange);
22759
- if (!result.ok)
22760
- return result;
22761
- const match = result.value.find((e) => e.tradingSymbol === symbol && e.exchange === exchange);
22762
- if (!match) {
22763
- return Err(new Error(`Instrument ${symbol} not found on ${exchange} (Dhan)`));
22764
- }
22765
- return Ok(match);
22766
- }
22767
22841
  normalize(raw) {
22768
22842
  const fields = raw;
22769
22843
  const securityId = fields["SEM_SMST_SECURITY_ID"] ?? fields["securityId"] ?? "";
@@ -22822,13 +22896,23 @@ class DhanBroker {
22822
22896
  id = "dhan";
22823
22897
  name = "Dhan";
22824
22898
  instruments;
22899
+ has = {
22900
+ bulkDump: true,
22901
+ searchAPI: true,
22902
+ optionChain: true,
22903
+ historicalCandles: true,
22904
+ websocket: true,
22905
+ pnlReport: false
22906
+ };
22825
22907
  session = null;
22826
22908
  socket = null;
22827
22909
  clientId = "";
22828
22910
  instrumentsImpl;
22829
- constructor() {
22911
+ _iMaster;
22912
+ constructor(options) {
22830
22913
  this.instrumentsImpl = new DhanInstruments;
22831
22914
  this.instruments = this.instrumentsImpl;
22915
+ this._iMaster = new InstrumentMaster(options);
22832
22916
  }
22833
22917
  async authenticate(creds) {
22834
22918
  this.clientId = creds.userId ?? "";
@@ -22850,9 +22934,31 @@ class DhanBroker {
22850
22934
  return null;
22851
22935
  return new Date(this.session.expiresAt);
22852
22936
  }
22937
+ async sync(force) {
22938
+ return this._iMaster.syncBroker(this.id, this.instruments, force);
22939
+ }
22940
+ resolve(input) {
22941
+ return this._iMaster.resolve(input);
22942
+ }
22943
+ search(query, exchange, instrumentType, limit) {
22944
+ return this._iMaster.search(query, exchange, instrumentType, limit);
22945
+ }
22853
22946
  async placeOrder(params) {
22854
22947
  try {
22855
- const dhanParams = toOrderParams3(params, this.clientId);
22948
+ let resolvedParams = params;
22949
+ if (!params.exchange) {
22950
+ const resolved = this._iMaster.resolve(params.tradingSymbol);
22951
+ if (!resolved.ok) {
22952
+ const asyncResolved = await this._iMaster.resolveAsync(params.tradingSymbol, this.id, this.instruments);
22953
+ if (!asyncResolved.ok) {
22954
+ 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.`));
22955
+ }
22956
+ resolvedParams = { ...params, exchange: asyncResolved.value.exchange };
22957
+ } else {
22958
+ resolvedParams = { ...params, exchange: resolved.value.exchange };
22959
+ }
22960
+ }
22961
+ const dhanParams = toOrderParams3(resolvedParams, this.clientId);
22856
22962
  const res = await this.request("POST", "/orders", dhanParams);
22857
22963
  return Ok({
22858
22964
  orderId: res.orderId,
@@ -22866,20 +22972,18 @@ class DhanBroker {
22866
22972
  }
22867
22973
  async modifyOrder(orderId, params) {
22868
22974
  try {
22975
+ const rawOrder = await this.request("GET", `/orders/${orderId}`);
22976
+ const original = Array.isArray(rawOrder) ? rawOrder[0] : rawOrder;
22869
22977
  const payload = {
22870
22978
  dhanClientId: this.clientId,
22871
- orderId
22979
+ orderId,
22980
+ orderType: params.type ? ORDER_TYPE_TO_DHAN[params.type] ?? original.orderType : original.orderType,
22981
+ validity: params.validity ?? original.validity ?? "DAY",
22982
+ quantity: params.quantity ?? original.quantity,
22983
+ price: params.price ?? original.price
22872
22984
  };
22873
- if (params.price !== undefined)
22874
- payload["price"] = params.price;
22875
- if (params.quantity !== undefined)
22876
- payload["quantity"] = params.quantity;
22877
22985
  if (params.triggerPrice !== undefined)
22878
22986
  payload["triggerPrice"] = params.triggerPrice;
22879
- if (params.type !== undefined)
22880
- payload["orderType"] = params.type;
22881
- if (params.validity !== undefined)
22882
- payload["validity"] = params.validity;
22883
22987
  const res = await this.request("PUT", `/orders/${orderId}`, payload);
22884
22988
  return Ok({
22885
22989
  orderId: res.orderId ?? orderId,
@@ -22946,7 +23050,11 @@ class DhanBroker {
22946
23050
  return Ok([]);
22947
23051
  return Ok(raw.map((h) => fromHolding3(h)));
22948
23052
  } catch (err) {
22949
- return Err(err instanceof Error ? err : new Error(String(err)));
23053
+ const msg = err instanceof Error ? err.message : String(err);
23054
+ if (msg.includes("No holdings available") || msg.includes("DH-1111")) {
23055
+ return Ok([]);
23056
+ }
23057
+ return Err(err instanceof Error ? err : new Error(msg));
22950
23058
  }
22951
23059
  }
22952
23060
  async getLTP(symbols) {
@@ -23506,6 +23614,14 @@ class PaperBroker {
23506
23614
  id = "paper";
23507
23615
  name = "Paper Trading";
23508
23616
  instruments;
23617
+ has = {
23618
+ bulkDump: false,
23619
+ searchAPI: false,
23620
+ optionChain: false,
23621
+ historicalCandles: false,
23622
+ websocket: false,
23623
+ pnlReport: true
23624
+ };
23509
23625
  session = null;
23510
23626
  engine;
23511
23627
  positions = new Map;
@@ -23516,6 +23632,7 @@ class PaperBroker {
23516
23632
  initialBalance;
23517
23633
  subscriptions = new Map;
23518
23634
  connectionState = "DISCONNECTED";
23635
+ _iMaster;
23519
23636
  dataSource = null;
23520
23637
  dataSourceSub = null;
23521
23638
  subIdCounter = 0;
@@ -23524,6 +23641,10 @@ class PaperBroker {
23524
23641
  this.initialBalance = config?.initialBalance ?? 1e7;
23525
23642
  this.balance = this.initialBalance;
23526
23643
  this.engine = new PaperFillEngine(config?.fillConfig);
23644
+ this._iMaster = new InstrumentMaster({
23645
+ redis: config?.redis,
23646
+ instrumentFilePath: config?.instrumentFilePath
23647
+ });
23527
23648
  this.engine.setFillCallback((orderId, update) => {
23528
23649
  this.onOrderFill(orderId, update);
23529
23650
  });
@@ -23531,6 +23652,20 @@ class PaperBroker {
23531
23652
  setDataSource(broker) {
23532
23653
  this.dataSource = broker;
23533
23654
  }
23655
+ async sync(_force) {
23656
+ return Ok({
23657
+ brokerId: "paper",
23658
+ lastSyncAt: Date.now(),
23659
+ instrumentCount: 0,
23660
+ source: "network"
23661
+ });
23662
+ }
23663
+ resolve(input) {
23664
+ return this._iMaster.resolve(input);
23665
+ }
23666
+ search(query, exchange, instrumentType, limit) {
23667
+ return this._iMaster.search(query, exchange, instrumentType, limit);
23668
+ }
23534
23669
  async authenticate(_creds) {
23535
23670
  this.session = {
23536
23671
  accessToken: "paper-session",
@@ -23798,7 +23933,8 @@ class PaperBroker {
23798
23933
  for (const sub of this.subscriptions.values()) {}
23799
23934
  }
23800
23935
  updatePositionOnFill(order, update) {
23801
- const { tradingSymbol, exchange, side, product } = order.params;
23936
+ const { tradingSymbol, side, product } = order.params;
23937
+ const exchange = order.params.exchange ?? "NSE";
23802
23938
  const posKey = `${exchange}:${tradingSymbol}:${product}`;
23803
23939
  if (!this.positions.has(posKey)) {
23804
23940
  this.positions.set(posKey, {
@@ -23855,7 +23991,7 @@ class PaperBroker {
23855
23991
  brokerOrderId: pending.orderId,
23856
23992
  broker: "paper",
23857
23993
  tradingSymbol: pending.params.tradingSymbol,
23858
- exchange: pending.params.exchange,
23994
+ exchange: pending.params.exchange ?? "NSE",
23859
23995
  side: pending.params.side,
23860
23996
  type: pending.params.type,
23861
23997
  product: pending.params.product,
@@ -23874,17 +24010,17 @@ class PaperBroker {
23874
24010
  }
23875
24011
  }
23876
24012
  // src/index.ts
23877
- var VERSION = "0.2.0";
23878
- function createBroker(brokerId) {
24013
+ var VERSION = "0.3.0";
24014
+ function createBroker(brokerId, options) {
23879
24015
  switch (brokerId) {
23880
24016
  case "zerodha":
23881
- return new ZerodhaBroker;
24017
+ return new ZerodhaBroker(options);
23882
24018
  case "finvasia":
23883
- return new FinvasiaBroker;
24019
+ return new FinvasiaBroker(options);
23884
24020
  case "dhan":
23885
- return new DhanBroker;
24021
+ return new DhanBroker(options);
23886
24022
  case "paper":
23887
- return new PaperBroker;
24023
+ return new PaperBroker(options);
23888
24024
  default: {
23889
24025
  const exhaustive = brokerId;
23890
24026
  throw new Error(`Unknown broker: ${exhaustive}`);