nsekit 0.1.2 → 0.3.0

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/dist/index.js CHANGED
@@ -12626,10 +12626,10 @@ var require_axios = __commonJS((exports, module) => {
12626
12626
  }
12627
12627
  }
12628
12628
  var CanceledError$1 = CanceledError;
12629
- function settle(resolve, reject, response) {
12629
+ function settle(resolve2, reject, response) {
12630
12630
  const validateStatus = response.config.validateStatus;
12631
12631
  if (!response.status || !validateStatus || validateStatus(response.status)) {
12632
- resolve(response);
12632
+ resolve2(response);
12633
12633
  } else {
12634
12634
  reject(new AxiosError$1("Request failed with status code " + response.status, [AxiosError$1.ERR_BAD_REQUEST, AxiosError$1.ERR_BAD_RESPONSE][Math.floor(response.status / 100) - 4], response.config, response.request, response));
12635
12635
  }
@@ -13203,7 +13203,7 @@ var require_axios = __commonJS((exports, module) => {
13203
13203
  }
13204
13204
  var isHttpAdapterSupported = typeof process !== "undefined" && utils$1.kindOf(process) === "process";
13205
13205
  var wrapAsync = (asyncExecutor) => {
13206
- return new Promise((resolve, reject) => {
13206
+ return new Promise((resolve2, reject) => {
13207
13207
  let onDone;
13208
13208
  let isDone;
13209
13209
  const done = (value, isRejected) => {
@@ -13214,7 +13214,7 @@ var require_axios = __commonJS((exports, module) => {
13214
13214
  };
13215
13215
  const _resolve = (value) => {
13216
13216
  done(value);
13217
- resolve(value);
13217
+ resolve2(value);
13218
13218
  };
13219
13219
  const _reject = (reason) => {
13220
13220
  done(reason, true);
@@ -13266,7 +13266,7 @@ var require_axios = __commonJS((exports, module) => {
13266
13266
  }
13267
13267
  };
13268
13268
  var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) {
13269
- return wrapAsync(async function dispatchHttpRequest(resolve, reject, onDone) {
13269
+ return wrapAsync(async function dispatchHttpRequest(resolve2, reject, onDone) {
13270
13270
  let { data, lookup, family, httpVersion = 1, http2Options } = config;
13271
13271
  const { responseType, responseEncoding } = config;
13272
13272
  const method = config.method.toUpperCase();
@@ -13347,7 +13347,7 @@ var require_axios = __commonJS((exports, module) => {
13347
13347
  }
13348
13348
  let convertedData;
13349
13349
  if (method !== "GET") {
13350
- return settle(resolve, reject, {
13350
+ return settle(resolve2, reject, {
13351
13351
  status: 405,
13352
13352
  statusText: "method not allowed",
13353
13353
  headers: {},
@@ -13369,7 +13369,7 @@ var require_axios = __commonJS((exports, module) => {
13369
13369
  } else if (responseType === "stream") {
13370
13370
  convertedData = stream__default["default"].Readable.from(convertedData);
13371
13371
  }
13372
- return settle(resolve, reject, {
13372
+ return settle(resolve2, reject, {
13373
13373
  data: convertedData,
13374
13374
  status: 200,
13375
13375
  statusText: "OK",
@@ -13556,7 +13556,7 @@ var require_axios = __commonJS((exports, module) => {
13556
13556
  };
13557
13557
  if (responseType === "stream") {
13558
13558
  response.data = responseStream;
13559
- settle(resolve, reject, response);
13559
+ settle(resolve2, reject, response);
13560
13560
  } else {
13561
13561
  const responseBuffer = [];
13562
13562
  let totalResponseBytes = 0;
@@ -13595,7 +13595,7 @@ var require_axios = __commonJS((exports, module) => {
13595
13595
  } catch (err) {
13596
13596
  return reject(AxiosError$1.from(err, null, config, response.request, response));
13597
13597
  }
13598
- settle(resolve, reject, response);
13598
+ settle(resolve2, reject, response);
13599
13599
  });
13600
13600
  }
13601
13601
  abortEmitter.once("abort", (err) => {
@@ -13815,7 +13815,7 @@ var require_axios = __commonJS((exports, module) => {
13815
13815
  };
13816
13816
  var isXHRAdapterSupported = typeof XMLHttpRequest !== "undefined";
13817
13817
  var xhrAdapter = isXHRAdapterSupported && function(config) {
13818
- return new Promise(function dispatchXhrRequest(resolve, reject) {
13818
+ return new Promise(function dispatchXhrRequest(resolve2, reject) {
13819
13819
  const _config = resolveConfig(config);
13820
13820
  let requestData = _config.data;
13821
13821
  const requestHeaders = AxiosHeaders$1.from(_config.headers).normalize();
@@ -13847,7 +13847,7 @@ var require_axios = __commonJS((exports, module) => {
13847
13847
  request
13848
13848
  };
13849
13849
  settle(function _resolve(value) {
13850
- resolve(value);
13850
+ resolve2(value);
13851
13851
  done();
13852
13852
  }, function _reject(err) {
13853
13853
  reject(err);
@@ -14198,8 +14198,8 @@ var require_axios = __commonJS((exports, module) => {
14198
14198
  responseType = responseType || "text";
14199
14199
  let responseData = await resolvers[utils$1.findKey(resolvers, responseType) || "text"](response, config);
14200
14200
  !isStreamResponse && unsubscribe && unsubscribe();
14201
- return await new Promise((resolve, reject) => {
14202
- settle(resolve, reject, {
14201
+ return await new Promise((resolve2, reject) => {
14202
+ settle(resolve2, reject, {
14203
14203
  data: responseData,
14204
14204
  headers: AxiosHeaders$1.from(response.headers),
14205
14205
  status: response.status,
@@ -14547,8 +14547,8 @@ var require_axios = __commonJS((exports, module) => {
14547
14547
  throw new TypeError("executor must be a function.");
14548
14548
  }
14549
14549
  let resolvePromise;
14550
- this.promise = new Promise(function promiseExecutor(resolve) {
14551
- resolvePromise = resolve;
14550
+ this.promise = new Promise(function promiseExecutor(resolve2) {
14551
+ resolvePromise = resolve2;
14552
14552
  });
14553
14553
  const token = this;
14554
14554
  this.promise.then((cancel) => {
@@ -14562,9 +14562,9 @@ var require_axios = __commonJS((exports, module) => {
14562
14562
  });
14563
14563
  this.promise.then = (onfulfilled) => {
14564
14564
  let _resolve;
14565
- const promise = new Promise((resolve) => {
14566
- token.subscribe(resolve);
14567
- _resolve = resolve;
14565
+ const promise = new Promise((resolve2) => {
14566
+ token.subscribe(resolve2);
14567
+ _resolve = resolve2;
14568
14568
  }).then(onfulfilled);
14569
14569
  promise.cancel = function reject() {
14570
14570
  token.unsubscribe(_resolve);
@@ -18454,7 +18454,7 @@ var require_connect2 = __commonJS((exports) => {
18454
18454
  return `${this.default_login_uri}?api_key=${this.api_key}&v=3`;
18455
18455
  }
18456
18456
  generateSession(request_token, api_secret) {
18457
- return new Promise((resolve, reject) => {
18457
+ return new Promise((resolve2, reject) => {
18458
18458
  const checksum = (0, sha256_1.default)(this.api_key + request_token + api_secret).toString();
18459
18459
  const p = this._post("api.token", {
18460
18460
  api_key: this.api_key,
@@ -18465,7 +18465,7 @@ var require_connect2 = __commonJS((exports) => {
18465
18465
  if (resp && resp.access_token) {
18466
18466
  this.setAccessToken(resp.access_token);
18467
18467
  }
18468
- return resolve(resp);
18468
+ return resolve2(resp);
18469
18469
  }).catch(function(err) {
18470
18470
  return reject(err);
18471
18471
  });
@@ -18478,7 +18478,7 @@ var require_connect2 = __commonJS((exports) => {
18478
18478
  });
18479
18479
  }
18480
18480
  renewAccessToken(refresh_token, api_secret) {
18481
- return new Promise((resolve, reject) => {
18481
+ return new Promise((resolve2, reject) => {
18482
18482
  const checksum = (0, sha256_1.default)(this.api_key + refresh_token + api_secret).toString();
18483
18483
  const p = this._post("api.token.renew", {
18484
18484
  api_key: this.api_key,
@@ -18489,7 +18489,7 @@ var require_connect2 = __commonJS((exports) => {
18489
18489
  if (resp && resp.access_token) {
18490
18490
  this.setAccessToken(resp.access_token);
18491
18491
  }
18492
- return resolve(resp);
18492
+ return resolve2(resp);
18493
18493
  }).catch(function(err) {
18494
18494
  return reject(err);
18495
18495
  });
@@ -19297,44 +19297,63 @@ class InstrumentNotFoundError extends BrokerError {
19297
19297
  }
19298
19298
  }
19299
19299
  // src/instruments/instrument-master.ts
19300
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
19301
+ import { join, resolve } from "path";
19302
+ var DEFAULT_INSTRUMENT_PATH = "./InstrumentDump";
19303
+
19300
19304
  class InstrumentMaster {
19301
19305
  instruments = new Map;
19302
19306
  tokenIndex = new Map;
19303
19307
  syncMeta = new Map;
19304
19308
  redis;
19309
+ instrumentFilePath;
19305
19310
  constructor(options) {
19306
19311
  this.redis = options?.redis ?? null;
19312
+ this.instrumentFilePath = resolve(options?.instrumentFilePath ?? DEFAULT_INSTRUMENT_PATH);
19307
19313
  }
19308
19314
  async syncBroker(brokerId, brokerInstruments, force = false) {
19309
19315
  try {
19310
- const existingMeta = this.syncMeta.get(brokerId);
19311
- if (!force && existingMeta && brokerInstruments.checkDumpChanged) {
19312
- const changed = await brokerInstruments.checkDumpChanged({
19313
- etag: existingMeta.etag,
19314
- lastModified: existingMeta.lastModified
19315
- });
19316
- if (!changed) {
19317
- return Ok(existingMeta);
19316
+ if (!force) {
19317
+ const diskEntries = this.loadFromDisk(brokerId);
19318
+ if (diskEntries) {
19319
+ this.clearBrokerEntries(brokerId);
19320
+ for (const entry of diskEntries) {
19321
+ this.mergeEntry(brokerId, entry);
19322
+ }
19323
+ const meta2 = {
19324
+ brokerId,
19325
+ lastSyncAt: Date.now(),
19326
+ instrumentCount: diskEntries.length,
19327
+ source: "disk"
19328
+ };
19329
+ this.syncMeta.set(brokerId, meta2);
19330
+ if (this.redis) {
19331
+ this.cacheInstrumentsToRedis();
19332
+ }
19333
+ return Ok(meta2);
19318
19334
  }
19319
19335
  }
19320
19336
  this.clearBrokerEntries(brokerId);
19321
- let count = 0;
19337
+ const entries = [];
19322
19338
  if (brokerInstruments.capabilities.bulkDump && brokerInstruments.streamDump) {
19323
19339
  for await (const raw of brokerInstruments.streamDump()) {
19324
19340
  const entry = brokerInstruments.normalize(raw);
19341
+ entries.push(entry);
19325
19342
  this.mergeEntry(brokerId, entry);
19326
- count++;
19327
19343
  }
19328
19344
  }
19345
+ if (this.redis) {
19346
+ this.cacheInstrumentsToRedis();
19347
+ } else {
19348
+ this.saveToDisk(brokerId, entries);
19349
+ }
19329
19350
  const meta = {
19330
19351
  brokerId,
19331
19352
  lastSyncAt: Date.now(),
19332
- instrumentCount: count
19353
+ instrumentCount: entries.length,
19354
+ source: "network"
19333
19355
  };
19334
19356
  this.syncMeta.set(brokerId, meta);
19335
- if (this.redis) {
19336
- this.cacheInstrumentsToRedis();
19337
- }
19338
19357
  return Ok(meta);
19339
19358
  } catch (err) {
19340
19359
  return Err(err instanceof Error ? err : new Error(String(err)));
@@ -19348,9 +19367,8 @@ class InstrumentMaster {
19348
19367
  this.syncMeta.set(brokerId, {
19349
19368
  brokerId,
19350
19369
  lastSyncAt: Date.now(),
19351
- etag: existing?.etag,
19352
- lastModified: existing?.lastModified,
19353
- instrumentCount: (existing?.instrumentCount ?? 0) + entries.length
19370
+ instrumentCount: (existing?.instrumentCount ?? 0) + entries.length,
19371
+ source: "network"
19354
19372
  });
19355
19373
  }
19356
19374
  search(query, exchange, instrumentType, limit = 50) {
@@ -19396,6 +19414,52 @@ class InstrumentMaster {
19396
19414
  return;
19397
19415
  return this.instruments.get(entry.key);
19398
19416
  }
19417
+ resolve(input) {
19418
+ const { exchange, symbol } = this.parseInput(input);
19419
+ if (exchange) {
19420
+ const key = this.makeKey(exchange, symbol);
19421
+ const inst = this.instruments.get(key);
19422
+ if (inst)
19423
+ return Ok(inst);
19424
+ return Err(new Error(`Symbol "${symbol}" not found on ${exchange}`));
19425
+ }
19426
+ const matches = [];
19427
+ for (const inst of this.instruments.values()) {
19428
+ if (inst.tradingSymbol === symbol)
19429
+ matches.push(inst);
19430
+ }
19431
+ if (matches.length === 0) {
19432
+ return Err(new Error(`Symbol "${symbol}" not found`));
19433
+ }
19434
+ if (matches.length === 1) {
19435
+ return Ok(matches[0]);
19436
+ }
19437
+ if (matches.length === 2) {
19438
+ const exchanges = new Set(matches.map((m) => m.exchange));
19439
+ if (exchanges.has("NSE") && exchanges.has("BSE")) {
19440
+ return Ok(matches.find((m) => m.exchange === "NSE"));
19441
+ }
19442
+ }
19443
+ const list = matches.slice(0, 5).map((m) => `${m.exchange}:${m.tradingSymbol}`).join(", ");
19444
+ return Err(new Error(`Multiple matches found. Specify exchange: ${list}`));
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
+ }
19399
19463
  async getOptionChainCached(underlying, expiry) {
19400
19464
  const expiryDate = expiry.toISOString().slice(0, 10);
19401
19465
  const cacheKey = `optchain:${underlying.toUpperCase()}:${expiryDate}`;
@@ -19442,6 +19506,9 @@ class InstrumentMaster {
19442
19506
  get size() {
19443
19507
  return this.instruments.size;
19444
19508
  }
19509
+ get filePath() {
19510
+ return this.instrumentFilePath;
19511
+ }
19445
19512
  getSyncMeta(brokerId) {
19446
19513
  return this.syncMeta.get(brokerId);
19447
19514
  }
@@ -19465,8 +19532,39 @@ class InstrumentMaster {
19465
19532
  this.tokenIndex.clear();
19466
19533
  this.syncMeta.clear();
19467
19534
  }
19535
+ getDiskCachePath(brokerId) {
19536
+ return join(this.instrumentFilePath, `${brokerId}-instruments.json`);
19537
+ }
19538
+ ensureDirectory() {
19539
+ if (!existsSync(this.instrumentFilePath)) {
19540
+ mkdirSync(this.instrumentFilePath, { recursive: true });
19541
+ }
19542
+ }
19543
+ loadFromDisk(brokerId) {
19544
+ const filePath = this.getDiskCachePath(brokerId);
19545
+ try {
19546
+ if (!existsSync(filePath))
19547
+ return null;
19548
+ const raw = readFileSync(filePath, "utf-8");
19549
+ const parsed = JSON.parse(raw);
19550
+ if (!Array.isArray(parsed))
19551
+ return null;
19552
+ return parsed;
19553
+ } catch {
19554
+ return null;
19555
+ }
19556
+ }
19557
+ saveToDisk(brokerId, entries) {
19558
+ try {
19559
+ this.ensureDirectory();
19560
+ const filePath = this.getDiskCachePath(brokerId);
19561
+ const stripped = entries.map(({ raw, ...rest }) => rest);
19562
+ writeFileSync(filePath, JSON.stringify(stripped), "utf-8");
19563
+ } catch {}
19564
+ }
19468
19565
  mergeEntry(brokerId, entry) {
19469
- const key = this.makeKey(entry.exchange, entry.tradingSymbol);
19566
+ const canonical = this.buildCanonicalSymbol(entry.underlying, entry.instrumentType, entry.strike, entry.expiry);
19567
+ const key = this.makeKey(entry.exchange, canonical);
19470
19568
  let instrument = this.instruments.get(key);
19471
19569
  if (!instrument) {
19472
19570
  instrument = {
@@ -19477,7 +19575,7 @@ class InstrumentMaster {
19477
19575
  strike: entry.strike,
19478
19576
  lotSize: entry.lotSize,
19479
19577
  tickSize: entry.tickSize,
19480
- tradingSymbol: entry.tradingSymbol,
19578
+ tradingSymbol: canonical,
19481
19579
  brokerTokens: {}
19482
19580
  };
19483
19581
  this.instruments.set(key, instrument);
@@ -19571,6 +19669,35 @@ class InstrumentMaster {
19571
19669
  }
19572
19670
  return true;
19573
19671
  }
19672
+ formatExpiry(date) {
19673
+ const d = date instanceof Date ? date : new Date(date);
19674
+ const dd = String(d.getDate()).padStart(2, "0");
19675
+ const mmm = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"][d.getMonth()];
19676
+ const yy = String(d.getFullYear()).slice(-2);
19677
+ return `${dd}${mmm}${yy}`;
19678
+ }
19679
+ buildCanonicalSymbol(underlying, instrumentType, strike, expiry) {
19680
+ const name = underlying.toUpperCase();
19681
+ if (instrumentType === "EQ")
19682
+ return name;
19683
+ const exp = expiry ? this.formatExpiry(expiry) : "";
19684
+ if (instrumentType === "FUT")
19685
+ return `${name}-FUT-${exp}`;
19686
+ const s = strike != null ? String(strike) : "0";
19687
+ return `${name}-${s}-${instrumentType}-${exp}`;
19688
+ }
19689
+ parseInput(input) {
19690
+ const upper = input.trim().toUpperCase();
19691
+ const VALID = new Set(["NSE", "BSE", "NFO", "BFO", "MCX", "CDS"]);
19692
+ const idx = upper.indexOf(":");
19693
+ if (idx >= 2 && idx <= 3) {
19694
+ const prefix = upper.slice(0, idx);
19695
+ if (VALID.has(prefix)) {
19696
+ return { exchange: prefix, symbol: upper.slice(idx + 1) };
19697
+ }
19698
+ }
19699
+ return { symbol: upper };
19700
+ }
19574
19701
  makeKey(exchange, tradingSymbol) {
19575
19702
  return `${exchange}:${tradingSymbol}`;
19576
19703
  }
@@ -20455,33 +20582,6 @@ class ZerodhaInstruments {
20455
20582
  yield row;
20456
20583
  }
20457
20584
  }
20458
- async checkDumpChanged(lastSync) {
20459
- const response = await fetch(KITE_INSTRUMENTS_URL, { method: "HEAD" });
20460
- if (!response.ok)
20461
- return true;
20462
- const etag = response.headers.get("etag");
20463
- const lastModified = response.headers.get("last-modified");
20464
- if (etag && lastSync.etag)
20465
- return etag !== lastSync.etag;
20466
- if (lastModified && lastSync.lastModified)
20467
- return lastModified !== lastSync.lastModified;
20468
- return true;
20469
- }
20470
- async resolve(symbol, exchange) {
20471
- try {
20472
- for await (const raw of this.streamDump()) {
20473
- const fields = raw;
20474
- const rowSymbol = fields["tradingsymbol"] ?? "";
20475
- const rowExchange = fields["exchange"] ?? "";
20476
- if (rowSymbol === symbol && rowExchange === exchange) {
20477
- return Ok(this.normalize(raw));
20478
- }
20479
- }
20480
- return Err(new Error(`Instrument ${symbol} not found on ${exchange} (Zerodha)`));
20481
- } catch (err) {
20482
- return Err(err instanceof Error ? err : new Error(String(err)));
20483
- }
20484
- }
20485
20585
  normalize(raw) {
20486
20586
  const fields = raw;
20487
20587
  const tradingSymbol = fields["tradingsymbol"] ?? fields[COL.TRADINGSYMBOL] ?? "";
@@ -20515,13 +20615,23 @@ class ZerodhaBroker {
20515
20615
  id = "zerodha";
20516
20616
  name = "Zerodha";
20517
20617
  instruments;
20618
+ has = {
20619
+ bulkDump: true,
20620
+ searchAPI: false,
20621
+ optionChain: false,
20622
+ historicalCandles: true,
20623
+ websocket: true,
20624
+ pnlReport: false
20625
+ };
20518
20626
  session = null;
20519
20627
  kite = null;
20520
20628
  socket = null;
20521
20629
  apiKey = "";
20522
20630
  KiteConnect = null;
20523
- constructor() {
20631
+ _iMaster;
20632
+ constructor(options) {
20524
20633
  this.instruments = new ZerodhaInstruments;
20634
+ this._iMaster = new InstrumentMaster(options);
20525
20635
  }
20526
20636
  async authenticate(creds) {
20527
20637
  this.apiKey = creds.apiKey;
@@ -20544,10 +20654,34 @@ class ZerodhaBroker {
20544
20654
  return null;
20545
20655
  return new Date(this.session.expiresAt);
20546
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
+ }
20547
20666
  async placeOrder(params) {
20548
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
+ }
20549
20683
  const kite = await this.ensureKite();
20550
- const kiteParams = toOrderParams(params);
20684
+ const kiteParams = toOrderParams(resolvedParams);
20551
20685
  const res = await kite.placeOrder("regular", kiteParams);
20552
20686
  return Ok({
20553
20687
  orderId: res.order_id,
@@ -21466,25 +21600,7 @@ class FinvasiaInstruments {
21466
21600
  }
21467
21601
  }
21468
21602
  }
21469
- async checkDumpChanged(lastSync) {
21470
- const firstFile = Object.values(SHOONYA_INSTRUMENT_FILES)[0];
21471
- const url = `${SHOONYA_INSTRUMENTS_BASE}${firstFile}`;
21472
- try {
21473
- const response = await fetch(url, { method: "HEAD" });
21474
- if (!response.ok)
21475
- return true;
21476
- const etag = response.headers.get("etag");
21477
- const lastModified = response.headers.get("last-modified");
21478
- if (etag && lastSync.etag)
21479
- return etag !== lastSync.etag;
21480
- if (lastModified && lastSync.lastModified)
21481
- return lastModified !== lastSync.lastModified;
21482
- return true;
21483
- } catch {
21484
- return true;
21485
- }
21486
- }
21487
- async search(query, exchange) {
21603
+ async searchAPI(query, exchange) {
21488
21604
  if (!this.accessToken) {
21489
21605
  return Err(new Error("Finvasia search requires authentication. Call setCredentials() first."));
21490
21606
  }
@@ -21524,16 +21640,6 @@ class FinvasiaInstruments {
21524
21640
  return Err(err instanceof Error ? err : new Error(String(err)));
21525
21641
  }
21526
21642
  }
21527
- async resolve(symbol, exchange) {
21528
- const result = await this.search(symbol, exchange);
21529
- if (!result.ok)
21530
- return result;
21531
- const match = result.value.find((e) => e.tradingSymbol === symbol && e.exchange === exchange);
21532
- if (!match) {
21533
- return Err(new Error(`Instrument ${symbol} not found on ${exchange} (Finvasia)`));
21534
- }
21535
- return Ok(match);
21536
- }
21537
21643
  normalize(raw) {
21538
21644
  const fields = raw;
21539
21645
  const exchangeRaw = fields["exchange"] ?? fields["exch"] ?? fields["_exchange"] ?? "NSE";
@@ -21569,12 +21675,22 @@ class FinvasiaBroker {
21569
21675
  id = "finvasia";
21570
21676
  name = "Finvasia (Shoonya)";
21571
21677
  instruments;
21678
+ has = {
21679
+ bulkDump: true,
21680
+ searchAPI: true,
21681
+ optionChain: true,
21682
+ historicalCandles: true,
21683
+ websocket: true,
21684
+ pnlReport: false
21685
+ };
21572
21686
  session = null;
21573
21687
  socket = null;
21574
21688
  instrumentsImpl;
21575
- constructor() {
21689
+ _iMaster;
21690
+ constructor(options) {
21576
21691
  this.instrumentsImpl = new FinvasiaInstruments;
21577
21692
  this.instruments = this.instrumentsImpl;
21693
+ this._iMaster = new InstrumentMaster(options);
21578
21694
  }
21579
21695
  async authenticate(creds) {
21580
21696
  const result = await authenticate2(creds);
@@ -21595,10 +21711,34 @@ class FinvasiaBroker {
21595
21711
  return null;
21596
21712
  return new Date(this.session.expiresAt);
21597
21713
  }
21714
+ async sync(force) {
21715
+ return this._iMaster.syncBroker(this.id, this.instruments, force);
21716
+ }
21717
+ resolve(input) {
21718
+ return this._iMaster.resolve(input);
21719
+ }
21720
+ search(query, exchange, instrumentType, limit) {
21721
+ return this._iMaster.search(query, exchange, instrumentType, limit);
21722
+ }
21598
21723
  async placeOrder(params) {
21599
21724
  try {
21725
+ let resolvedParams = params;
21726
+ if (!params.exchange) {
21727
+ const resolved = this._iMaster.resolve(params.tradingSymbol);
21728
+ if (!resolved.ok) {
21729
+ const asyncResolved = await this._iMaster.resolveAsync(params.tradingSymbol, this.id, this.instruments);
21730
+ if (!asyncResolved.ok) {
21731
+ 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.`));
21732
+ }
21733
+ const nativeSymbol = asyncResolved.value.brokerTokens.finvasia?.tsym ?? params.tradingSymbol;
21734
+ resolvedParams = { ...params, exchange: asyncResolved.value.exchange, tradingSymbol: nativeSymbol };
21735
+ } else {
21736
+ const nativeSymbol = resolved.value.brokerTokens.finvasia?.tsym ?? params.tradingSymbol;
21737
+ resolvedParams = { ...params, exchange: resolved.value.exchange, tradingSymbol: nativeSymbol };
21738
+ }
21739
+ }
21600
21740
  const uid = this.session?.userId ?? "";
21601
- const shoonyaParams = toOrderParams2(params, uid, uid);
21741
+ const shoonyaParams = toOrderParams2(resolvedParams, uid, uid);
21602
21742
  const res = await this.post("PlaceOrder", shoonyaParams);
21603
21743
  if (res.stat !== "Ok" || !res.norenordno) {
21604
21744
  return Err(new Error(`Order placement failed: ${res.emsg ?? "Unknown error"}`));
@@ -22585,23 +22725,7 @@ class DhanInstruments {
22585
22725
  yield row;
22586
22726
  }
22587
22727
  }
22588
- async checkDumpChanged(lastSync) {
22589
- try {
22590
- const response = await fetch(DHAN_INSTRUMENTS_URL, { method: "HEAD" });
22591
- if (!response.ok)
22592
- return true;
22593
- const etag = response.headers.get("etag");
22594
- const lastModified = response.headers.get("last-modified");
22595
- if (etag && lastSync.etag)
22596
- return etag !== lastSync.etag;
22597
- if (lastModified && lastSync.lastModified)
22598
- return lastModified !== lastSync.lastModified;
22599
- return true;
22600
- } catch {
22601
- return true;
22602
- }
22603
- }
22604
- async search(query, exchange) {
22728
+ async searchAPI(query, exchange) {
22605
22729
  if (!this.accessToken) {
22606
22730
  return Err(new Error("Dhan search requires authentication. Call setCredentials() first."));
22607
22731
  }
@@ -22644,16 +22768,6 @@ class DhanInstruments {
22644
22768
  return Err(err instanceof Error ? err : new Error(String(err)));
22645
22769
  }
22646
22770
  }
22647
- async resolve(symbol, exchange) {
22648
- const result = await this.search(symbol, exchange);
22649
- if (!result.ok)
22650
- return result;
22651
- const match = result.value.find((e) => e.tradingSymbol === symbol && e.exchange === exchange);
22652
- if (!match) {
22653
- return Err(new Error(`Instrument ${symbol} not found on ${exchange} (Dhan)`));
22654
- }
22655
- return Ok(match);
22656
- }
22657
22771
  normalize(raw) {
22658
22772
  const fields = raw;
22659
22773
  const securityId = fields["SEM_SMST_SECURITY_ID"] ?? fields["securityId"] ?? "";
@@ -22712,13 +22826,23 @@ class DhanBroker {
22712
22826
  id = "dhan";
22713
22827
  name = "Dhan";
22714
22828
  instruments;
22829
+ has = {
22830
+ bulkDump: true,
22831
+ searchAPI: true,
22832
+ optionChain: true,
22833
+ historicalCandles: true,
22834
+ websocket: true,
22835
+ pnlReport: false
22836
+ };
22715
22837
  session = null;
22716
22838
  socket = null;
22717
22839
  clientId = "";
22718
22840
  instrumentsImpl;
22719
- constructor() {
22841
+ _iMaster;
22842
+ constructor(options) {
22720
22843
  this.instrumentsImpl = new DhanInstruments;
22721
22844
  this.instruments = this.instrumentsImpl;
22845
+ this._iMaster = new InstrumentMaster(options);
22722
22846
  }
22723
22847
  async authenticate(creds) {
22724
22848
  this.clientId = creds.userId ?? "";
@@ -22740,9 +22864,31 @@ class DhanBroker {
22740
22864
  return null;
22741
22865
  return new Date(this.session.expiresAt);
22742
22866
  }
22867
+ async sync(force) {
22868
+ return this._iMaster.syncBroker(this.id, this.instruments, force);
22869
+ }
22870
+ resolve(input) {
22871
+ return this._iMaster.resolve(input);
22872
+ }
22873
+ search(query, exchange, instrumentType, limit) {
22874
+ return this._iMaster.search(query, exchange, instrumentType, limit);
22875
+ }
22743
22876
  async placeOrder(params) {
22744
22877
  try {
22745
- const dhanParams = toOrderParams3(params, this.clientId);
22878
+ let resolvedParams = params;
22879
+ if (!params.exchange) {
22880
+ const resolved = this._iMaster.resolve(params.tradingSymbol);
22881
+ if (!resolved.ok) {
22882
+ const asyncResolved = await this._iMaster.resolveAsync(params.tradingSymbol, this.id, this.instruments);
22883
+ if (!asyncResolved.ok) {
22884
+ 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
+ }
22886
+ resolvedParams = { ...params, exchange: asyncResolved.value.exchange };
22887
+ } else {
22888
+ resolvedParams = { ...params, exchange: resolved.value.exchange };
22889
+ }
22890
+ }
22891
+ const dhanParams = toOrderParams3(resolvedParams, this.clientId);
22746
22892
  const res = await this.request("POST", "/orders", dhanParams);
22747
22893
  return Ok({
22748
22894
  orderId: res.orderId,
@@ -23396,6 +23542,14 @@ class PaperBroker {
23396
23542
  id = "paper";
23397
23543
  name = "Paper Trading";
23398
23544
  instruments;
23545
+ has = {
23546
+ bulkDump: false,
23547
+ searchAPI: false,
23548
+ optionChain: false,
23549
+ historicalCandles: false,
23550
+ websocket: false,
23551
+ pnlReport: true
23552
+ };
23399
23553
  session = null;
23400
23554
  engine;
23401
23555
  positions = new Map;
@@ -23406,6 +23560,7 @@ class PaperBroker {
23406
23560
  initialBalance;
23407
23561
  subscriptions = new Map;
23408
23562
  connectionState = "DISCONNECTED";
23563
+ _iMaster;
23409
23564
  dataSource = null;
23410
23565
  dataSourceSub = null;
23411
23566
  subIdCounter = 0;
@@ -23414,6 +23569,10 @@ class PaperBroker {
23414
23569
  this.initialBalance = config?.initialBalance ?? 1e7;
23415
23570
  this.balance = this.initialBalance;
23416
23571
  this.engine = new PaperFillEngine(config?.fillConfig);
23572
+ this._iMaster = new InstrumentMaster({
23573
+ redis: config?.redis,
23574
+ instrumentFilePath: config?.instrumentFilePath
23575
+ });
23417
23576
  this.engine.setFillCallback((orderId, update) => {
23418
23577
  this.onOrderFill(orderId, update);
23419
23578
  });
@@ -23421,6 +23580,20 @@ class PaperBroker {
23421
23580
  setDataSource(broker) {
23422
23581
  this.dataSource = broker;
23423
23582
  }
23583
+ async sync(_force) {
23584
+ return Ok({
23585
+ brokerId: "paper",
23586
+ lastSyncAt: Date.now(),
23587
+ instrumentCount: 0,
23588
+ source: "network"
23589
+ });
23590
+ }
23591
+ resolve(input) {
23592
+ return this._iMaster.resolve(input);
23593
+ }
23594
+ search(query, exchange, instrumentType, limit) {
23595
+ return this._iMaster.search(query, exchange, instrumentType, limit);
23596
+ }
23424
23597
  async authenticate(_creds) {
23425
23598
  this.session = {
23426
23599
  accessToken: "paper-session",
@@ -23688,7 +23861,8 @@ class PaperBroker {
23688
23861
  for (const sub of this.subscriptions.values()) {}
23689
23862
  }
23690
23863
  updatePositionOnFill(order, update) {
23691
- const { tradingSymbol, exchange, side, product } = order.params;
23864
+ const { tradingSymbol, side, product } = order.params;
23865
+ const exchange = order.params.exchange ?? "NSE";
23692
23866
  const posKey = `${exchange}:${tradingSymbol}:${product}`;
23693
23867
  if (!this.positions.has(posKey)) {
23694
23868
  this.positions.set(posKey, {
@@ -23745,7 +23919,7 @@ class PaperBroker {
23745
23919
  brokerOrderId: pending.orderId,
23746
23920
  broker: "paper",
23747
23921
  tradingSymbol: pending.params.tradingSymbol,
23748
- exchange: pending.params.exchange,
23922
+ exchange: pending.params.exchange ?? "NSE",
23749
23923
  side: pending.params.side,
23750
23924
  type: pending.params.type,
23751
23925
  product: pending.params.product,
@@ -23764,17 +23938,17 @@ class PaperBroker {
23764
23938
  }
23765
23939
  }
23766
23940
  // src/index.ts
23767
- var VERSION = "0.1.2";
23768
- function createBroker(brokerId) {
23941
+ var VERSION = "0.3.0";
23942
+ function createBroker(brokerId, options) {
23769
23943
  switch (brokerId) {
23770
23944
  case "zerodha":
23771
- return new ZerodhaBroker;
23945
+ return new ZerodhaBroker(options);
23772
23946
  case "finvasia":
23773
- return new FinvasiaBroker;
23947
+ return new FinvasiaBroker(options);
23774
23948
  case "dhan":
23775
- return new DhanBroker;
23949
+ return new DhanBroker(options);
23776
23950
  case "paper":
23777
- return new PaperBroker;
23951
+ return new PaperBroker(options);
23778
23952
  default: {
23779
23953
  const exhaustive = brokerId;
23780
23954
  throw new Error(`Unknown broker: ${exhaustive}`);