nsekit 0.3.1 → 0.3.3

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 (55) hide show
  1. package/README.md +3 -2
  2. package/dist/brokers/dhan/DhanBroker.d.ts +17 -0
  3. package/dist/brokers/dhan/DhanBroker.d.ts.map +1 -1
  4. package/dist/brokers/dhan/dhan-auth.d.ts +41 -7
  5. package/dist/brokers/dhan/dhan-auth.d.ts.map +1 -1
  6. package/dist/brokers/dhan/dhan-constants.d.ts +1 -0
  7. package/dist/brokers/dhan/dhan-constants.d.ts.map +1 -1
  8. package/dist/brokers/dhan/dhan-instruments.d.ts.map +1 -1
  9. package/dist/brokers/dhan/dhan-mapper.d.ts +1 -0
  10. package/dist/brokers/dhan/dhan-mapper.d.ts.map +1 -1
  11. package/dist/brokers/dhan/dhan-socket.d.ts +8 -0
  12. package/dist/brokers/dhan/dhan-socket.d.ts.map +1 -1
  13. package/dist/brokers/finvasia/FinvasiaBroker.d.ts +43 -0
  14. package/dist/brokers/finvasia/FinvasiaBroker.d.ts.map +1 -1
  15. package/dist/brokers/finvasia/finvasia-auth.d.ts +40 -4
  16. package/dist/brokers/finvasia/finvasia-auth.d.ts.map +1 -1
  17. package/dist/brokers/finvasia/finvasia-constants.d.ts +3 -1
  18. package/dist/brokers/finvasia/finvasia-constants.d.ts.map +1 -1
  19. package/dist/brokers/finvasia/finvasia-instruments.d.ts +1 -1
  20. package/dist/brokers/finvasia/finvasia-instruments.d.ts.map +1 -1
  21. package/dist/brokers/finvasia/finvasia-mapper.d.ts +5 -1
  22. package/dist/brokers/finvasia/finvasia-mapper.d.ts.map +1 -1
  23. package/dist/brokers/finvasia/finvasia-socket.d.ts +9 -2
  24. package/dist/brokers/finvasia/finvasia-socket.d.ts.map +1 -1
  25. package/dist/brokers/fivepaisa/FivePaisaBroker.d.ts +70 -0
  26. package/dist/brokers/fivepaisa/FivePaisaBroker.d.ts.map +1 -0
  27. package/dist/brokers/fivepaisa/fivepaisa-auth.d.ts +53 -0
  28. package/dist/brokers/fivepaisa/fivepaisa-auth.d.ts.map +1 -0
  29. package/dist/brokers/fivepaisa/fivepaisa-constants.d.ts +57 -0
  30. package/dist/brokers/fivepaisa/fivepaisa-constants.d.ts.map +1 -0
  31. package/dist/brokers/fivepaisa/fivepaisa-instruments.d.ts +34 -0
  32. package/dist/brokers/fivepaisa/fivepaisa-instruments.d.ts.map +1 -0
  33. package/dist/brokers/fivepaisa/fivepaisa-mapper.d.ts +170 -0
  34. package/dist/brokers/fivepaisa/fivepaisa-mapper.d.ts.map +1 -0
  35. package/dist/brokers/fivepaisa/fivepaisa-socket.d.ts +67 -0
  36. package/dist/brokers/fivepaisa/fivepaisa-socket.d.ts.map +1 -0
  37. package/dist/brokers/paper/PaperBroker.d.ts +3 -0
  38. package/dist/brokers/paper/PaperBroker.d.ts.map +1 -1
  39. package/dist/brokers/zerodha/ZerodhaBroker.d.ts +3 -0
  40. package/dist/brokers/zerodha/ZerodhaBroker.d.ts.map +1 -1
  41. package/dist/brokers/zerodha/zerodha-instruments.d.ts.map +1 -1
  42. package/dist/index.d.ts +2 -2
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +1949 -82
  45. package/dist/instruments/instrument-master.d.ts +4 -0
  46. package/dist/instruments/instrument-master.d.ts.map +1 -1
  47. package/dist/interfaces/broker.interface.d.ts +7 -0
  48. package/dist/interfaces/broker.interface.d.ts.map +1 -1
  49. package/dist/types/broker.d.ts +7 -0
  50. package/dist/types/broker.d.ts.map +1 -1
  51. package/dist/types/common.d.ts +2 -2
  52. package/dist/types/common.d.ts.map +1 -1
  53. package/dist/types/instruments.d.ts +4 -0
  54. package/dist/types/instruments.d.ts.map +1 -1
  55. package/package.json +51 -51
package/dist/index.js CHANGED
@@ -19407,6 +19407,14 @@ class InstrumentMaster {
19407
19407
  const key = this.makeKey(exchange, tradingSymbol);
19408
19408
  return this.instruments.get(key);
19409
19409
  }
19410
+ getIndices() {
19411
+ const indices = [];
19412
+ for (const inst of this.instruments.values()) {
19413
+ if (inst.instrumentType === "IDX")
19414
+ indices.push(inst);
19415
+ }
19416
+ return indices;
19417
+ }
19410
19418
  getByToken(brokerToken, brokerId) {
19411
19419
  const tokenKey = `${brokerId}:${brokerToken}`;
19412
19420
  const entry = this.tokenIndex.get(tokenKey);
@@ -19448,8 +19456,9 @@ class InstrumentMaster {
19448
19456
  if (local.ok)
19449
19457
  return local;
19450
19458
  if (brokerInstruments.capabilities.searchAPI && brokerInstruments.searchAPI) {
19451
- const { symbol } = this.parseInput(input);
19452
- const apiResult = await brokerInstruments.searchAPI(symbol);
19459
+ const { exchange, symbol } = this.parseInput(input);
19460
+ const inferredExchange = exchange ?? (/-(?:CE|PE|FUT)-/.test(symbol) ? "NFO" : undefined);
19461
+ const apiResult = await brokerInstruments.searchAPI(symbol, inferredExchange);
19453
19462
  if (apiResult.ok && apiResult.value.length > 0) {
19454
19463
  for (const entry of apiResult.value)
19455
19464
  this.mergeEntry(brokerId, entry);
@@ -19595,6 +19604,11 @@ class InstrumentMaster {
19595
19604
  securityId: entry.brokerToken,
19596
19605
  sid: entry.brokerSymbol
19597
19606
  };
19607
+ } else if (brokerId === "fivepaisa") {
19608
+ instrument.brokerTokens.fivepaisa = {
19609
+ scripCode: entry.brokerToken,
19610
+ exchangeType: entry.raw?.["ExchType"] ?? "C"
19611
+ };
19598
19612
  }
19599
19613
  if (entry.expiry && !instrument.expiry)
19600
19614
  instrument.expiry = entry.expiry;
@@ -19626,7 +19640,9 @@ class InstrumentMaster {
19626
19640
  delete instrument.brokerTokens.finvasia;
19627
19641
  else if (brokerId === "dhan")
19628
19642
  delete instrument.brokerTokens.dhan;
19629
- const hasAnyBroker = instrument.brokerTokens.zerodha !== undefined || instrument.brokerTokens.finvasia !== undefined || instrument.brokerTokens.dhan !== undefined;
19643
+ else if (brokerId === "fivepaisa")
19644
+ delete instrument.brokerTokens.fivepaisa;
19645
+ const hasAnyBroker = instrument.brokerTokens.zerodha !== undefined || instrument.brokerTokens.finvasia !== undefined || instrument.brokerTokens.dhan !== undefined || instrument.brokerTokens.fivepaisa !== undefined;
19630
19646
  if (!hasAnyBroker) {
19631
19647
  this.instruments.delete(key);
19632
19648
  }
@@ -20192,8 +20208,8 @@ function safeSide(raw) {
20192
20208
  }
20193
20209
  function parseTimestamp(ts) {
20194
20210
  if (!ts)
20195
- return Date.now();
20196
- return new Date(ts).getTime();
20211
+ return 0;
20212
+ return new Date(ts).getTime() || 0;
20197
20213
  }
20198
20214
  function inferInstrumentType(symbol, exchange) {
20199
20215
  if (exchange === "NSE" || exchange === "BSE")
@@ -20592,8 +20608,9 @@ class ZerodhaInstruments {
20592
20608
  const lotSizeRaw = fields["lot_size"] ?? fields[COL.LOT_SIZE] ?? "1";
20593
20609
  const tickSizeRaw = fields["tick_size"] ?? fields[COL.TICK_SIZE] ?? "0.05";
20594
20610
  const instTypeRaw = fields["instrument_type"] ?? fields[COL.INSTRUMENT_TYPE] ?? "EQ";
20611
+ const segmentRaw = fields["segment"] ?? fields[COL.SEGMENT] ?? "";
20595
20612
  const exchange = EXCHANGE_MAP[exchangeRaw] ?? "NSE";
20596
- const instrumentType = mapInstrumentType(instTypeRaw);
20613
+ const instrumentType = segmentRaw.toUpperCase() === "INDICES" ? "IDX" : mapInstrumentType(instTypeRaw);
20597
20614
  return {
20598
20615
  tradingSymbol,
20599
20616
  exchange,
@@ -20654,6 +20671,9 @@ class ZerodhaBroker {
20654
20671
  return null;
20655
20672
  return new Date(this.session.expiresAt);
20656
20673
  }
20674
+ getSession() {
20675
+ return this.session;
20676
+ }
20657
20677
  async sync(force) {
20658
20678
  return this._iMaster.syncBroker(this.id, this.instruments, force);
20659
20679
  }
@@ -20839,6 +20859,25 @@ class ZerodhaBroker {
20839
20859
  return Err(err instanceof Error ? err : new Error(String(err)));
20840
20860
  }
20841
20861
  }
20862
+ toBrokerToken(instrument, format = "ws") {
20863
+ if (format === "rest") {
20864
+ const kiteExch = EXCHANGE_TO_KITE[instrument.exchange] ?? instrument.exchange;
20865
+ return `${kiteExch}:${instrument.tradingSymbol}`;
20866
+ }
20867
+ const it = instrument.brokerTokens.zerodha?.instrument_token;
20868
+ if (it == null)
20869
+ return null;
20870
+ return String(it);
20871
+ }
20872
+ getIndexTokens(format = "ws") {
20873
+ const result = new Map;
20874
+ for (const inst of this._iMaster.getIndices()) {
20875
+ const token = this.toBrokerToken(inst, format);
20876
+ if (token)
20877
+ result.set(inst.underlying.toUpperCase(), token);
20878
+ }
20879
+ return result;
20880
+ }
20842
20881
  subscribeTicks(tokens, cb) {
20843
20882
  this.ensureSocket();
20844
20883
  return this.socket.subscribe(tokens, cb);
@@ -21011,8 +21050,9 @@ var VALIDITY_FROM_SHOONYA = {
21011
21050
  DAY: "DAY",
21012
21051
  IOC: "IOC"
21013
21052
  };
21014
- var SHOONYA_API_BASE = "https://api.shoonya.com/NorenWClientTP";
21053
+ var SHOONYA_API_BASE = "https://api.shoonya.com/NorenWClientAPI";
21015
21054
  var SHOONYA_WS_URL = "wss://api.shoonya.com/NorenWSTP/";
21055
+ var SHOONYA_WS_OAUTH_URL = "wss://api.shoonya.com/NorenWSAPI/";
21016
21056
  var SHOONYA_INSTRUMENTS_BASE = "https://api.shoonya.com/";
21017
21057
  var SHOONYA_INSTRUMENT_FILES = {
21018
21058
  NSE: "NSE_symbols.txt.zip",
@@ -21032,6 +21072,7 @@ var CANDLE_INTERVAL_TO_SHOONYA = {
21032
21072
 
21033
21073
  // src/brokers/finvasia/finvasia-auth.ts
21034
21074
  import crypto2 from "crypto";
21075
+ var SHOONYA_OAUTH_BASE = "https://trade.shoonya.com/OAuthlogin/authorize/oauth";
21035
21076
  function generateTOTP(secret) {
21036
21077
  const base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
21037
21078
  const decoded = [];
@@ -21056,7 +21097,7 @@ function generateTOTP(secret) {
21056
21097
  return String(code % 1e6).padStart(6, "0");
21057
21098
  }
21058
21099
  async function authenticate2(creds) {
21059
- const { userId, password, totpSecret, apiKey } = creds;
21100
+ const { userId, password, totpSecret, apiKey, vendorCode } = creds;
21060
21101
  if (!userId)
21061
21102
  return Err(new Error("Missing userId in credentials"));
21062
21103
  if (!password)
@@ -21068,28 +21109,42 @@ async function authenticate2(creds) {
21068
21109
  try {
21069
21110
  const totp = generateTOTP(totpSecret);
21070
21111
  const passwordHash = crypto2.createHash("sha256").update(password).digest("hex");
21071
- const appKey = crypto2.createHash("sha256").update(`${userId}|${apiKey}`).digest("hex");
21112
+ const vc = vendorCode || "NOREN_API";
21113
+ const appKey = crypto2.createHash("sha256").update(`${userId}|S3cur3!d`).digest("hex");
21072
21114
  const jData = JSON.stringify({
21073
21115
  source: "API",
21074
- apkversion: "1.0.0",
21116
+ apkversion: "1",
21075
21117
  uid: userId,
21076
21118
  pwd: passwordHash,
21077
21119
  factor2: totp,
21078
- vc: creds.vendorCode ?? apiKey,
21120
+ vc,
21079
21121
  appkey: appKey,
21080
21122
  imei: "nsekit"
21081
21123
  });
21082
- const response = await fetch(`${SHOONYA_API_BASE}/QuickAuth`, {
21083
- method: "POST",
21084
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
21085
- body: `jData=${jData}`
21086
- });
21087
- const text = await response.text();
21088
- let json;
21089
- try {
21090
- json = JSON.parse(text);
21091
- } catch {
21092
- return Err(new Error(`Shoonya auth: invalid response (${response.status}): ${text.slice(0, 200)}`));
21124
+ const AUTH_HOSTS = [
21125
+ SHOONYA_API_BASE,
21126
+ "https://trade.shoonya.com/NorenWClientAPI"
21127
+ ];
21128
+ let text = "";
21129
+ let json = null;
21130
+ for (const host of AUTH_HOSTS) {
21131
+ const response = await fetch(`${host}/QuickAuth`, {
21132
+ method: "POST",
21133
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
21134
+ body: `jData=${jData}`
21135
+ });
21136
+ text = await response.text();
21137
+ try {
21138
+ json = JSON.parse(text);
21139
+ break;
21140
+ } catch {
21141
+ if (response.status >= 500)
21142
+ continue;
21143
+ return Err(new Error(`Shoonya auth: invalid response (${response.status}): ${text.slice(0, 200)}`));
21144
+ }
21145
+ }
21146
+ if (!json) {
21147
+ return Err(new Error(`Shoonya auth: all hosts returned errors. Last: ${text.slice(0, 200)}`));
21093
21148
  }
21094
21149
  if (json.stat !== "Ok" || !json.susertoken) {
21095
21150
  return Err(new Error(`Shoonya login failed: ${json.emsg ?? "Unknown error"}`));
@@ -21111,6 +21166,87 @@ async function authenticate2(creds) {
21111
21166
  async function refreshSession2(_session) {
21112
21167
  return Err(new Error("Finvasia/Shoonya does not support session refresh. Re-authenticate with TOTP."));
21113
21168
  }
21169
+ function getOAuthURL(clientId) {
21170
+ return `${SHOONYA_OAUTH_BASE}?client_id=${encodeURIComponent(clientId)}`;
21171
+ }
21172
+ async function exchangeAuthCode(authCode, secretCode, clientId, uid) {
21173
+ if (!authCode)
21174
+ return Err(new Error("Missing authCode"));
21175
+ if (!secretCode)
21176
+ return Err(new Error("Missing secretCode"));
21177
+ if (!clientId)
21178
+ return Err(new Error("Missing clientId"));
21179
+ if (!uid)
21180
+ return Err(new Error("Missing uid"));
21181
+ try {
21182
+ const checksum = crypto2.createHash("sha256").update(clientId + secretCode + authCode).digest("hex");
21183
+ const jData = JSON.stringify({
21184
+ code: authCode,
21185
+ checksum,
21186
+ uid
21187
+ });
21188
+ const response = await fetch(`${SHOONYA_API_BASE}/GenAcsTok`, {
21189
+ method: "POST",
21190
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
21191
+ body: `jData=${jData}`
21192
+ });
21193
+ const text = await response.text();
21194
+ let json;
21195
+ try {
21196
+ json = JSON.parse(text);
21197
+ } catch {
21198
+ return Err(new Error(`Shoonya GenAcsTok: invalid response (${response.status}): ${text.slice(0, 200)}`));
21199
+ }
21200
+ if (json.stat !== "Ok" || !json.access_token) {
21201
+ const rawEmsg = String(json.emsg ?? "");
21202
+ const lower = rawEmsg.toLowerCase();
21203
+ let friendly;
21204
+ if (/(:\s*5\b|no data|invalid code|code expired|invalid authcode)/i.test(rawEmsg)) {
21205
+ friendly = "Invalid or expired auth code. Auth codes are single-use and expire within a few minutes \u2014 please log in to Shoonya again and copy a fresh code.";
21206
+ } else if (lower.includes("checksum") || lower.includes("invalid signature")) {
21207
+ friendly = "Invalid checksum. Verify that Client ID and Secret Code are correct in the broker settings.";
21208
+ } else if (lower.includes("invalid uid") || lower.includes("invalid user")) {
21209
+ friendly = "Invalid User ID. Check the User ID in the broker settings.";
21210
+ } else if (lower.includes("client_id") || lower.includes("clientid")) {
21211
+ friendly = "Invalid Client ID. It should typically be in the format <UID>_U (e.g. FN116677_U).";
21212
+ } else if (rawEmsg) {
21213
+ friendly = `Shoonya OAuth token exchange failed: ${rawEmsg}`;
21214
+ } else {
21215
+ friendly = "Shoonya OAuth token exchange failed with an unknown error.";
21216
+ }
21217
+ return Err(new Error(friendly));
21218
+ }
21219
+ const session = {
21220
+ accessToken: String(json.access_token),
21221
+ refreshToken: json.refresh_token ? String(json.refresh_token) : undefined,
21222
+ expiresAt: getEndOfDay2(),
21223
+ ttlSeconds: secondsUntilEndOfDay2(),
21224
+ brokerId: "finvasia",
21225
+ userId: String(json.USERID ?? json.uid ?? uid),
21226
+ metadata: {
21227
+ actid: json.actid,
21228
+ uname: json.uname,
21229
+ brkname: json.brkname,
21230
+ lastAccess: json.lastaccesstime,
21231
+ authMethod: "oauth"
21232
+ }
21233
+ };
21234
+ return Ok(session);
21235
+ } catch (err) {
21236
+ return Err(err instanceof Error ? err : new Error(String(err)));
21237
+ }
21238
+ }
21239
+ function buildSessionFromToken(accessToken, uid, refreshToken) {
21240
+ return {
21241
+ accessToken,
21242
+ refreshToken,
21243
+ expiresAt: getEndOfDay2(),
21244
+ ttlSeconds: secondsUntilEndOfDay2(),
21245
+ brokerId: "finvasia",
21246
+ userId: uid,
21247
+ metadata: { authMethod: "oauth" }
21248
+ };
21249
+ }
21114
21250
  function isSessionValid2(session) {
21115
21251
  if (!session)
21116
21252
  return false;
@@ -21169,13 +21305,18 @@ function inferSide2(qty) {
21169
21305
  }
21170
21306
  function parseTimestamp2(ts) {
21171
21307
  if (!ts)
21172
- return Date.now();
21308
+ return 0;
21173
21309
  const asNum = Number(ts);
21174
21310
  if (!isNaN(asNum) && asNum > 1000000000000)
21175
21311
  return asNum;
21176
21312
  if (!isNaN(asNum) && asNum > 1e9)
21177
21313
  return asNum * 1000;
21178
- return new Date(ts).getTime() || Date.now();
21314
+ const shoonya = ts.match(/^(\d{2}:\d{2}:\d{2})\s+(\d{2})-(\d{2})-(\d{4})$/);
21315
+ if (shoonya) {
21316
+ const [, time, dd, mm, yyyy] = shoonya;
21317
+ return new Date(`${yyyy}-${mm}-${dd}T${time}`).getTime() || 0;
21318
+ }
21319
+ return new Date(ts).getTime() || 0;
21179
21320
  }
21180
21321
  function toOrderParams2(unified, uid, actid) {
21181
21322
  const params = {
@@ -21203,17 +21344,21 @@ function fromPosition2(pos) {
21203
21344
  const buyQty = num(pos.daybuyqty);
21204
21345
  const sellQty = num(pos.daysellqty);
21205
21346
  const realizedPnl = num(pos.rpnl);
21206
- const unrealizedPnl = num(pos.urmtom);
21347
+ const ltp = num(pos.lp);
21348
+ const entryPrice = num(pos.upldprc);
21349
+ const avgPrice = entryPrice > 0 ? entryPrice : num(pos.netavgprc);
21350
+ const unrealizedPnl = entryPrice > 0 ? (ltp - entryPrice) * netQty : num(pos.urmtom);
21351
+ const tradingSymbol = pos.tsym.replace(/-EQ$|-BE$|-BL$|-IL$|-IQ$/, "");
21207
21352
  return {
21208
21353
  broker: "finvasia",
21209
- tradingSymbol: pos.tsym,
21354
+ tradingSymbol,
21210
21355
  exchange: safeExchange2(pos.exch),
21211
21356
  side: inferSide2(netQty),
21212
21357
  quantity: Math.abs(netQty),
21213
21358
  buyQty,
21214
21359
  sellQty,
21215
- avgPrice: num(pos.netavgprc),
21216
- ltp: num(pos.lp),
21360
+ avgPrice,
21361
+ ltp,
21217
21362
  pnl: realizedPnl + unrealizedPnl,
21218
21363
  realizedPnl,
21219
21364
  unrealizedPnl,
@@ -21250,7 +21395,7 @@ function fromTick2(tick, symbolLookup) {
21250
21395
  return {
21251
21396
  broker: "finvasia",
21252
21397
  token: tick.tk,
21253
- tradingSymbol: symbolLookup ?? tick.tk,
21398
+ tradingSymbol: symbolLookup ?? tick.ts ?? tick.tk,
21254
21399
  ltp: num(tick.lp),
21255
21400
  open: num(tick.o),
21256
21401
  high: num(tick.h),
@@ -21265,15 +21410,17 @@ function fromTick2(tick, symbolLookup) {
21265
21410
  timestamp: parseTimestamp2(tick.ft)
21266
21411
  };
21267
21412
  }
21268
- function fromHolding2(holding) {
21413
+ function fromHolding2(holding, ltp = 0) {
21269
21414
  const first = holding.exch_tsym?.[0];
21270
21415
  const qty = num(holding.holdqty) + num(holding.dpqty) + num(holding.npoadqty) + num(holding.btstqty) - num(holding.usedqty);
21271
21416
  const avgPrice = num(holding.upldprc);
21272
- const ltp = num(holding.lp);
21417
+ ltp = num(holding.lp) || num(holding.c) || ltp;
21273
21418
  const pnl = (ltp - avgPrice) * qty;
21419
+ const rawSymbol = first?.tsym ?? "";
21420
+ const tradingSymbol = rawSymbol.replace(/-EQ$|-BE$|-BL$|-IL$|-IQ$/, "");
21274
21421
  return {
21275
21422
  broker: "finvasia",
21276
- tradingSymbol: first?.tsym ?? "",
21423
+ tradingSymbol,
21277
21424
  exchange: safeExchange2(first?.exch ?? "NSE"),
21278
21425
  isin: holding.isin ?? "",
21279
21426
  quantity: qty,
@@ -21327,26 +21474,37 @@ class FinvasiaSocket {
21327
21474
  subscribedTokens = new Set;
21328
21475
  userId;
21329
21476
  accessToken;
21477
+ authMethod;
21330
21478
  reconnectAttempts = 0;
21331
21479
  maxReconnects = 5;
21332
21480
  reconnectTimer = null;
21333
- constructor(userId, accessToken) {
21481
+ onAuthFailed;
21482
+ constructor(userId, accessToken, onAuthFailed, authMethod = "totp") {
21334
21483
  this.userId = userId;
21335
21484
  this.accessToken = accessToken;
21485
+ this.authMethod = authMethod;
21486
+ this.onAuthFailed = onAuthFailed;
21336
21487
  }
21337
21488
  connect() {
21338
21489
  if (this.state === "CONNECTED" || this.state === "CONNECTING")
21339
21490
  return;
21340
21491
  this.state = "CONNECTING";
21492
+ const wsUrl = this.authMethod === "oauth" ? SHOONYA_WS_OAUTH_URL : SHOONYA_WS_URL;
21341
21493
  try {
21342
- this.ws = new WS(SHOONYA_WS_URL);
21494
+ this.ws = new WS(wsUrl);
21343
21495
  } catch {
21344
21496
  this.state = "DISCONNECTED";
21345
21497
  return;
21346
21498
  }
21347
21499
  this.ws.on("open", () => {
21348
- console.log(`[FinvasiaSocket] WS open, sending auth for uid=${this.userId}`);
21349
- const authMsg = JSON.stringify({
21500
+ console.log(`[FinvasiaSocket] WS open, sending auth for uid=${this.userId} (method=${this.authMethod})`);
21501
+ const authMsg = this.authMethod === "oauth" ? JSON.stringify({
21502
+ t: "a",
21503
+ uid: this.userId,
21504
+ actid: this.userId,
21505
+ accesstoken: this.accessToken,
21506
+ source: "API"
21507
+ }) : JSON.stringify({
21350
21508
  t: "c",
21351
21509
  uid: this.userId,
21352
21510
  actid: this.userId,
@@ -21382,6 +21540,18 @@ class FinvasiaSocket {
21382
21540
  this.state = "DISCONNECTED";
21383
21541
  this.subscribedTokens.clear();
21384
21542
  }
21543
+ reconnect(newToken) {
21544
+ this.clearReconnectTimer();
21545
+ if (newToken)
21546
+ this.accessToken = newToken;
21547
+ this.reconnectAttempts = 0;
21548
+ if (this.ws) {
21549
+ this.ws.close();
21550
+ this.ws = null;
21551
+ }
21552
+ this.state = "DISCONNECTED";
21553
+ this.connect();
21554
+ }
21385
21555
  subscribe(tokens, callback, symbolMap) {
21386
21556
  const sub = {
21387
21557
  id: nextSubId3(),
@@ -21392,7 +21562,11 @@ class FinvasiaSocket {
21392
21562
  for (const t of tokens) {
21393
21563
  this.subscribedTokens.add(t);
21394
21564
  if (symbolMap?.has(t)) {
21395
- this.tokenToSymbol.set(t, symbolMap.get(t));
21565
+ const sym = symbolMap.get(t);
21566
+ this.tokenToSymbol.set(t, sym);
21567
+ const sep = t.indexOf("|");
21568
+ if (sep !== -1)
21569
+ this.tokenToSymbol.set(t.slice(sep + 1), sym);
21396
21570
  }
21397
21571
  }
21398
21572
  if (this.ws && this.state === "CONNECTED") {
@@ -21425,17 +21599,32 @@ class FinvasiaSocket {
21425
21599
  setSymbolMap(map) {
21426
21600
  for (const [token, symbol] of map) {
21427
21601
  this.tokenToSymbol.set(token, symbol);
21602
+ const sep = token.indexOf("|");
21603
+ if (sep !== -1) {
21604
+ this.tokenToSymbol.set(token.slice(sep + 1), symbol);
21605
+ }
21428
21606
  }
21429
21607
  }
21430
21608
  handleMessage(data) {
21431
21609
  const type = data["t"];
21432
- if (type === "ck") {
21610
+ if (type === "h") {
21611
+ if (this.ws && this.ws.readyState === WS.OPEN) {
21612
+ this.ws.send(JSON.stringify({ t: "h" }));
21613
+ }
21614
+ return;
21615
+ }
21616
+ if (type === "ck" || type === "ak") {
21433
21617
  if (data["s"] === "OK") {
21434
21618
  this.state = "CONNECTED";
21435
21619
  this.reconnectAttempts = 0;
21436
21620
  if (this.subscribedTokens.size > 0) {
21437
21621
  this.sendSubscribe(Array.from(this.subscribedTokens));
21438
21622
  }
21623
+ } else {
21624
+ console.error(`[FinvasiaSocket] Auth rejected (${type} NOT_OK) \u2014 token invalid, stopping reconnect`);
21625
+ this.reconnectAttempts = this.maxReconnects;
21626
+ this.state = "DISCONNECTED";
21627
+ this.onAuthFailed?.();
21439
21628
  }
21440
21629
  return;
21441
21630
  }
@@ -21444,6 +21633,10 @@ class FinvasiaSocket {
21444
21633
  const rawToken = tick.tk;
21445
21634
  const exch = data["e"] ?? "";
21446
21635
  const fullKey = exch ? `${exch}|${rawToken}` : rawToken;
21636
+ if (tick.ts && !this.tokenToSymbol.has(fullKey)) {
21637
+ this.tokenToSymbol.set(fullKey, tick.ts);
21638
+ this.tokenToSymbol.set(rawToken, tick.ts);
21639
+ }
21447
21640
  const symbol = this.tokenToSymbol.get(fullKey) ?? this.tokenToSymbol.get(rawToken);
21448
21641
  const unified = fromTick2(tick, symbol);
21449
21642
  unified.token = fullKey;
@@ -21497,6 +21690,27 @@ class FinvasiaSocket {
21497
21690
  }
21498
21691
 
21499
21692
  // src/brokers/finvasia/finvasia-instruments.ts
21693
+ import { inflateRawSync } from "zlib";
21694
+ function extractTextFromZip(buffer) {
21695
+ const bytes = new Uint8Array(buffer);
21696
+ if (bytes.length < 30 || bytes[0] !== 80 || bytes[1] !== 75 || bytes[2] !== 3 || bytes[3] !== 4) {
21697
+ return new TextDecoder().decode(buffer);
21698
+ }
21699
+ const view = new DataView(buffer);
21700
+ const compressionMethod = view.getUint16(8, true);
21701
+ const compressedSize = view.getUint32(18, true);
21702
+ const filenameLength = view.getUint16(26, true);
21703
+ const extraFieldLength = view.getUint16(28, true);
21704
+ const dataOffset = 30 + filenameLength + extraFieldLength;
21705
+ const compressedData = new Uint8Array(buffer, dataOffset, compressedSize);
21706
+ if (compressionMethod === 0) {
21707
+ return new TextDecoder().decode(compressedData);
21708
+ }
21709
+ if (compressionMethod === 8) {
21710
+ return new TextDecoder().decode(inflateRawSync(compressedData));
21711
+ }
21712
+ throw new Error(`Unsupported ZIP compression method: ${compressionMethod}`);
21713
+ }
21500
21714
  var COL2 = {
21501
21715
  EXCHANGE: 0,
21502
21716
  TOKEN: 1,
@@ -21509,6 +21723,18 @@ var COL2 = {
21509
21723
  STRIKE: 8,
21510
21724
  OPTION_TYPE: 9
21511
21725
  };
21726
+ var HEADER_MAP = {
21727
+ Exchange: "exchange",
21728
+ Token: "token",
21729
+ LotSize: "lot_size",
21730
+ Symbol: "symbol",
21731
+ TradingSymbol: "trading_symbol",
21732
+ Expiry: "expiry",
21733
+ Instrument: "instrument_type",
21734
+ OptionType: "option_type",
21735
+ StrikePrice: "strike",
21736
+ TickSize: "tick_size"
21737
+ };
21512
21738
  var EXCHANGE_MAP2 = {
21513
21739
  NSE: "NSE",
21514
21740
  BSE: "BSE",
@@ -21524,7 +21750,9 @@ function mapInstrumentType2(instType, optionType) {
21524
21750
  if (upper === "PE")
21525
21751
  return "PE";
21526
21752
  const it = instType?.toUpperCase();
21527
- if (it === "EQ" || it === "EQUITY" || it === "INDEX")
21753
+ if (it === "INDEX")
21754
+ return "IDX";
21755
+ if (it === "EQ" || it === "EQUITY")
21528
21756
  return "EQ";
21529
21757
  if (it?.startsWith("FUT") || it?.includes("FUT"))
21530
21758
  return "FUT";
@@ -21542,10 +21770,23 @@ function parseStrike2(raw) {
21542
21770
  const n = parseFloat(raw);
21543
21771
  return isNaN(n) || n === 0 ? undefined : n;
21544
21772
  }
21773
+ var UNDERLYING_ALIASES = {
21774
+ BSXOPT: "SENSEX",
21775
+ BSXFUT: "SENSEX",
21776
+ NFIDX: "NIFTY",
21777
+ BKIDX: "BANKNIFTY",
21778
+ "Nifty 50": "NIFTY50",
21779
+ "Nifty Bank": "BANKNIFTY",
21780
+ "Nifty Fin Services": "FINNIFTY",
21781
+ "NIFTY MID SELECT": "MIDCPNIFTY",
21782
+ "Nifty Next 50": "NIFTYNEXT50",
21783
+ INDIAVIX: "INDIAVIX"
21784
+ };
21545
21785
  function deriveUnderlying2(symbol, tradingSymbol, instType) {
21546
21786
  if (instType === "EQ")
21547
21787
  return symbol || tradingSymbol;
21548
- return symbol || tradingSymbol.replace(/\d.*/g, "");
21788
+ const raw = symbol || tradingSymbol.replace(/\d.*/g, "");
21789
+ return UNDERLYING_ALIASES[raw] ?? raw;
21549
21790
  }
21550
21791
 
21551
21792
  class FinvasiaInstruments {
@@ -21568,7 +21809,7 @@ class FinvasiaInstruments {
21568
21809
  const response = await fetch(url);
21569
21810
  if (!response.ok)
21570
21811
  continue;
21571
- const text = await response.text();
21812
+ const text = extractTextFromZip(await response.arrayBuffer());
21572
21813
  const lines = text.split(`
21573
21814
  `);
21574
21815
  let headers = null;
@@ -21576,10 +21817,10 @@ class FinvasiaInstruments {
21576
21817
  const trimmed = line.trim();
21577
21818
  if (!trimmed)
21578
21819
  continue;
21579
- const fields = trimmed.split("|");
21820
+ const fields = trimmed.split(",");
21580
21821
  if (!headers) {
21581
21822
  if (fields[0] === "Exchange" || fields[0] === "exch") {
21582
- headers = fields;
21823
+ headers = fields.map((h) => HEADER_MAP[h] ?? h.toLowerCase());
21583
21824
  continue;
21584
21825
  }
21585
21826
  headers = [];
@@ -21695,6 +21936,7 @@ class FinvasiaBroker {
21695
21936
  };
21696
21937
  session = null;
21697
21938
  socket = null;
21939
+ credentials = null;
21698
21940
  instrumentsImpl;
21699
21941
  _iMaster;
21700
21942
  constructor(options) {
@@ -21703,13 +21945,36 @@ class FinvasiaBroker {
21703
21945
  this._iMaster = new InstrumentMaster(options);
21704
21946
  }
21705
21947
  async authenticate(creds) {
21948
+ if (creds.accessToken && creds.userId) {
21949
+ const session = buildSessionFromToken(creds.accessToken, creds.userId, creds.refreshToken);
21950
+ this.credentials = creds;
21951
+ this.session = session;
21952
+ this.instrumentsImpl.setCredentials(session.userId, session.accessToken);
21953
+ return Ok(session);
21954
+ }
21955
+ if (creds.authCode) {
21956
+ if (!creds.clientId || !creds.secretCode || !creds.userId) {
21957
+ return Err(new Error("Finvasia OAuth: authCode requires clientId, secretCode, and userId"));
21958
+ }
21959
+ const result2 = await exchangeAuthCode(creds.authCode, creds.secretCode, creds.clientId, creds.userId);
21960
+ if (!result2.ok)
21961
+ return result2;
21962
+ this.credentials = creds;
21963
+ this.session = result2.value;
21964
+ this.instrumentsImpl.setCredentials(this.session.userId, this.session.accessToken);
21965
+ return result2;
21966
+ }
21706
21967
  const result = await authenticate2(creds);
21707
21968
  if (!result.ok)
21708
21969
  return result;
21970
+ this.credentials = creds;
21709
21971
  this.session = result.value;
21710
21972
  this.instrumentsImpl.setCredentials(this.session.userId, this.session.accessToken);
21711
21973
  return result;
21712
21974
  }
21975
+ getOAuthURL(clientId) {
21976
+ return getOAuthURL(clientId);
21977
+ }
21713
21978
  async refreshSession(session) {
21714
21979
  return refreshSession2(session);
21715
21980
  }
@@ -21721,6 +21986,19 @@ class FinvasiaBroker {
21721
21986
  return null;
21722
21987
  return new Date(this.session.expiresAt);
21723
21988
  }
21989
+ getSession() {
21990
+ return this.session;
21991
+ }
21992
+ async disconnect() {
21993
+ if (this.socket) {
21994
+ try {
21995
+ this.socket.disconnect();
21996
+ } catch {}
21997
+ this.socket = null;
21998
+ }
21999
+ this.session = null;
22000
+ this.credentials = null;
22001
+ }
21724
22002
  async sync(force) {
21725
22003
  return this._iMaster.syncBroker(this.id, this.instruments, force);
21726
22004
  }
@@ -21826,6 +22104,10 @@ class FinvasiaBroker {
21826
22104
  const uid = this.session?.userId ?? "";
21827
22105
  const raw = await this.post("OrderBook", { uid });
21828
22106
  if (!Array.isArray(raw)) {
22107
+ const obj = raw;
22108
+ if (obj.stat === "Not_Ok" && obj.emsg) {
22109
+ return Err(new Error(`OrderBook failed: ${obj.emsg}`));
22110
+ }
21829
22111
  return Ok([]);
21830
22112
  }
21831
22113
  return Ok(raw.map(fromOrder2));
@@ -21858,8 +22140,13 @@ class FinvasiaBroker {
21858
22140
  try {
21859
22141
  const uid = this.session?.userId ?? "";
21860
22142
  const raw = await this.post("PositionBook", { uid, actid: uid });
21861
- if (!Array.isArray(raw))
22143
+ if (!Array.isArray(raw)) {
22144
+ const obj = raw;
22145
+ if (obj.stat === "Not_Ok" && obj.emsg) {
22146
+ return Err(new Error(`PositionBook failed: ${obj.emsg}`));
22147
+ }
21862
22148
  return Ok([]);
22149
+ }
21863
22150
  return Ok(raw.map(fromPosition2));
21864
22151
  } catch (err) {
21865
22152
  return Err(err instanceof Error ? err : new Error(String(err)));
@@ -21873,8 +22160,13 @@ class FinvasiaBroker {
21873
22160
  actid: uid,
21874
22161
  prd: "C"
21875
22162
  });
21876
- if (!Array.isArray(raw))
22163
+ if (!Array.isArray(raw)) {
22164
+ const obj = raw;
22165
+ if (obj.stat === "Not_Ok" && obj.emsg) {
22166
+ return Err(new Error(`Holdings failed: ${obj.emsg}`));
22167
+ }
21877
22168
  return Ok([]);
22169
+ }
21878
22170
  return Ok(raw.map(fromHolding2));
21879
22171
  } catch (err) {
21880
22172
  return Err(err instanceof Error ? err : new Error(String(err)));
@@ -21998,10 +22290,30 @@ class FinvasiaBroker {
21998
22290
  return Err(err instanceof Error ? err : new Error(String(err)));
21999
22291
  }
22000
22292
  }
22293
+ toBrokerToken(instrument, format = "ws") {
22294
+ const token = instrument.brokerTokens.finvasia?.token;
22295
+ if (!token)
22296
+ return null;
22297
+ const sep = format === "rest" ? ":" : "|";
22298
+ return `${instrument.exchange}${sep}${token}`;
22299
+ }
22300
+ getIndexTokens(format = "ws") {
22301
+ const result = new Map;
22302
+ for (const inst of this._iMaster.getIndices()) {
22303
+ const token = this.toBrokerToken(inst, format);
22304
+ if (token)
22305
+ result.set(inst.underlying.toUpperCase(), token);
22306
+ }
22307
+ return result;
22308
+ }
22001
22309
  subscribeTicks(tokens, cb) {
22002
22310
  this.ensureSocket();
22003
22311
  return this.socket.subscribe(tokens, cb);
22004
22312
  }
22313
+ setSymbolMap(map) {
22314
+ this.ensureSocket();
22315
+ this.socket.setSymbolMap(map);
22316
+ }
22005
22317
  unsubscribeTicks(sub) {
22006
22318
  if (this.socket) {
22007
22319
  this.socket.unsubscribe(sub);
@@ -22018,18 +22330,25 @@ class FinvasiaBroker {
22018
22330
  actid: uid
22019
22331
  });
22020
22332
  if (raw.stat !== "Ok") {
22021
- return Err(new Error("Failed to fetch margins"));
22333
+ return Err(new Error(`Margins failed: ${raw.emsg ?? "Unknown error"}`));
22022
22334
  }
22023
22335
  const cash = parseFloat(raw["cash"] ?? "0");
22024
- const marginUsed = parseFloat(raw["marginused"] ?? "0");
22336
+ const marginUsed = parseFloat(raw["marginused"] ?? raw["peak_mar"] ?? "0");
22337
+ const blockedAmt = parseFloat(raw["blk_amt"] ?? "0");
22025
22338
  const collateral = parseFloat(raw["collateral"] ?? "0");
22339
+ const eqAvailable = parseFloat(raw["mr_der_a"] ?? "0");
22340
+ const comAvailable = parseFloat(raw["mr_com_a"] ?? "0");
22341
+ const available = cash;
22342
+ const used = marginUsed + blockedAmt;
22343
+ const total = available + used;
22026
22344
  return Ok({
22027
- available: cash - marginUsed,
22028
- used: marginUsed,
22029
- total: cash,
22345
+ available,
22346
+ used,
22347
+ total,
22030
22348
  collateral,
22031
22349
  segments: {
22032
- equity: { available: cash - marginUsed, used: marginUsed }
22350
+ equity: { available: eqAvailable, used: 0 },
22351
+ commodity: { available: comAvailable, used: 0 }
22033
22352
  }
22034
22353
  });
22035
22354
  } catch (err) {
@@ -22041,9 +22360,11 @@ class FinvasiaBroker {
22041
22360
  const margins = await this.getMargins();
22042
22361
  if (!margins.ok)
22043
22362
  return margins;
22363
+ const eq = margins.value.segments["equity"]?.available ?? 0;
22364
+ const com = margins.value.segments["commodity"]?.available ?? 0;
22044
22365
  return Ok({
22045
- equity: margins.value.available,
22046
- commodity: 0,
22366
+ equity: eq,
22367
+ commodity: com,
22047
22368
  total: margins.value.available
22048
22369
  });
22049
22370
  } catch (err) {
@@ -22090,10 +22411,18 @@ class FinvasiaBroker {
22090
22411
  if (!this.session)
22091
22412
  throw new Error("Not authenticated");
22092
22413
  const jData = JSON.stringify(payload);
22414
+ const isOAuth = this.session.metadata?.authMethod === "oauth";
22415
+ const headers = isOAuth ? {
22416
+ "Content-Type": "application/x-www-form-urlencoded",
22417
+ Authorization: `Bearer ${this.session.accessToken}`
22418
+ } : {
22419
+ "Content-Type": "application/x-www-form-urlencoded"
22420
+ };
22421
+ const body = isOAuth ? `jData=${jData}` : `jData=${jData}&jKey=${this.session.accessToken}`;
22093
22422
  const response = await fetch(`${SHOONYA_API_BASE}/${endpoint}`, {
22094
22423
  method: "POST",
22095
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
22096
- body: `jData=${jData}&jKey=${this.session.accessToken}`
22424
+ headers,
22425
+ body
22097
22426
  });
22098
22427
  const text = await response.text();
22099
22428
  let parsed;
@@ -22102,16 +22431,33 @@ class FinvasiaBroker {
22102
22431
  } catch {
22103
22432
  throw new Error(`Shoonya ${endpoint} failed (${response.status}): ${text.slice(0, 200)}`);
22104
22433
  }
22434
+ const obj = parsed;
22435
+ if (obj && obj["stat"] === "Not_Ok" && obj["emsg"]) {
22436
+ const emsg = String(obj["emsg"]).toLowerCase();
22437
+ if (emsg.includes("session") || emsg.includes("invalid") || emsg.includes("not logged")) {
22438
+ console.error(`[FinvasiaBroker] SESSION ERROR on ${endpoint}: ${obj["emsg"]} (token: ...${this.session.accessToken.slice(-8)})`);
22439
+ }
22440
+ }
22105
22441
  return parsed;
22106
22442
  }
22107
22443
  ensureSocket() {
22108
- if (this.socket)
22109
- return;
22110
22444
  if (!this.session)
22111
22445
  throw new Error("Cannot create socket without an active session");
22112
- this.socket = new FinvasiaSocket(this.session.userId, this.session.accessToken);
22446
+ if (this.socket) {
22447
+ if (this.socket.getConnectionState() === "DISCONNECTED") {
22448
+ this.socket.reconnect(this.session.accessToken);
22449
+ }
22450
+ return;
22451
+ }
22452
+ const authMethod = this.session.metadata?.authMethod === "oauth" ? "oauth" : "totp";
22453
+ this.socket = new FinvasiaSocket(this.session.userId, this.session.accessToken, () => {
22454
+ this.handleSocketAuthFailed();
22455
+ }, authMethod);
22113
22456
  this.socket.connect();
22114
22457
  }
22458
+ handleSocketAuthFailed() {
22459
+ console.warn("[FinvasiaBroker] Socket auth rejected (ck NOT_OK) \u2014 waiting for user-bot to re-authenticate");
22460
+ }
22115
22461
  }
22116
22462
  // src/brokers/dhan/dhan-constants.ts
22117
22463
  var EXCHANGE_TO_DHAN = {
@@ -22181,6 +22527,7 @@ var VALIDITY_FROM_DHAN = {
22181
22527
  IOC: "IOC"
22182
22528
  };
22183
22529
  var DHAN_API_BASE = "https://api.dhan.co/v2";
22530
+ var DHAN_AUTH_BASE = "https://auth.dhan.co";
22184
22531
  var DHAN_WS_URL = "wss://api-feed.dhan.co";
22185
22532
  var DHAN_INSTRUMENTS_URL = "https://images.dhan.co/api-data/api-scrip-master.csv";
22186
22533
  var CANDLE_INTERVAL_TO_DHAN = {
@@ -22211,12 +22558,12 @@ async function authenticate3(creds) {
22211
22558
  return Err(new Error(`Dhan auth validation failed (${response.status}): ${body}`));
22212
22559
  }
22213
22560
  const profile = await response.json();
22214
- const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000;
22561
+ const twentyFourHoursMs = 24 * 60 * 60 * 1000;
22215
22562
  const session = {
22216
22563
  accessToken,
22217
22564
  refreshToken: undefined,
22218
- expiresAt: Date.now() + thirtyDaysMs,
22219
- ttlSeconds: 30 * 24 * 60 * 60,
22565
+ expiresAt: Date.now() + twentyFourHoursMs,
22566
+ ttlSeconds: 24 * 60 * 60,
22220
22567
  brokerId: "dhan",
22221
22568
  userId: profile.clientId ?? clientId,
22222
22569
  metadata: { name: profile.name, email: profile.email }
@@ -22226,8 +22573,116 @@ async function authenticate3(creds) {
22226
22573
  return Err(err instanceof Error ? err : new Error(String(err)));
22227
22574
  }
22228
22575
  }
22229
- async function refreshSession3(_session) {
22230
- return Err(new Error("Dhan does not support token refresh. Generate a new access token from the Dhan developer portal."));
22576
+ async function generateConsent(creds) {
22577
+ const { apiKey: appId, apiSecret: appSecret, userId: clientId } = creds;
22578
+ if (!appId)
22579
+ return Err(new Error("Missing apiKey (App ID) in credentials"));
22580
+ if (!appSecret)
22581
+ return Err(new Error("Missing apiSecret (App Secret) in credentials"));
22582
+ if (!clientId)
22583
+ return Err(new Error("Missing userId (Dhan Client ID) in credentials"));
22584
+ try {
22585
+ const response = await fetch(`${DHAN_AUTH_BASE}/app/generate-consent?client_id=${clientId}`, {
22586
+ method: "POST",
22587
+ headers: {
22588
+ "Content-Type": "application/json",
22589
+ app_id: appId,
22590
+ app_secret: appSecret
22591
+ },
22592
+ body: JSON.stringify({})
22593
+ });
22594
+ if (!response.ok) {
22595
+ const body = await response.text();
22596
+ return Err(new Error(`Dhan generate-consent failed (${response.status}): ${body}`));
22597
+ }
22598
+ const data = await response.json();
22599
+ if (!data.consentAppId) {
22600
+ return Err(new Error(data.message ?? "Failed to generate consent \u2014 no consentAppId returned"));
22601
+ }
22602
+ const loginUrl = `${DHAN_AUTH_BASE}/login/consentApp-login?consentAppId=${data.consentAppId}`;
22603
+ return Ok({ consentAppId: data.consentAppId, loginUrl });
22604
+ } catch (err) {
22605
+ return Err(err instanceof Error ? err : new Error(String(err)));
22606
+ }
22607
+ }
22608
+ async function consumeConsent(creds, tokenId) {
22609
+ const { apiKey: appId, apiSecret: appSecret, userId: clientId } = creds;
22610
+ if (!appId)
22611
+ return Err(new Error("Missing apiKey (App ID) in credentials"));
22612
+ if (!appSecret)
22613
+ return Err(new Error("Missing apiSecret (App Secret) in credentials"));
22614
+ if (!tokenId)
22615
+ return Err(new Error("Missing tokenId from callback"));
22616
+ try {
22617
+ const response = await fetch(`${DHAN_AUTH_BASE}/app/consumeApp-consent?tokenId=${encodeURIComponent(tokenId)}`, {
22618
+ method: "POST",
22619
+ headers: {
22620
+ "Content-Type": "application/json",
22621
+ app_id: appId,
22622
+ app_secret: appSecret
22623
+ },
22624
+ body: JSON.stringify({})
22625
+ });
22626
+ if (!response.ok) {
22627
+ const body = await response.text();
22628
+ return Err(new Error(`Dhan consumeApp-consent failed (${response.status}): ${body}`));
22629
+ }
22630
+ const data = await response.json();
22631
+ const newToken = data.accessToken ?? data.token;
22632
+ if (!newToken) {
22633
+ return Err(new Error(data.message ?? "Failed to get access token \u2014 no token in response"));
22634
+ }
22635
+ const twentyFourHoursMs = 24 * 60 * 60 * 1000;
22636
+ const session = {
22637
+ accessToken: newToken,
22638
+ refreshToken: undefined,
22639
+ expiresAt: Date.now() + twentyFourHoursMs,
22640
+ ttlSeconds: 24 * 60 * 60,
22641
+ brokerId: "dhan",
22642
+ userId: clientId ?? "",
22643
+ metadata: { authMode: "oauth" }
22644
+ };
22645
+ return Ok(session);
22646
+ } catch (err) {
22647
+ return Err(err instanceof Error ? err : new Error(String(err)));
22648
+ }
22649
+ }
22650
+ async function refreshSession3(session) {
22651
+ const { accessToken, userId: clientId } = session;
22652
+ if (!accessToken)
22653
+ return Err(new Error("Missing accessToken in session"));
22654
+ if (!clientId)
22655
+ return Err(new Error("Missing userId (client_id) in session"));
22656
+ try {
22657
+ const response = await fetch(`${DHAN_API_BASE}/RenewToken`, {
22658
+ method: "GET",
22659
+ headers: {
22660
+ "Content-Type": "application/json",
22661
+ "access-token": accessToken,
22662
+ dhanClientId: clientId
22663
+ }
22664
+ });
22665
+ if (!response.ok) {
22666
+ const body = await response.text();
22667
+ return Err(new Error(`Dhan token renewal failed (${response.status}): ${body}`));
22668
+ }
22669
+ const data = await response.json();
22670
+ const newToken = data.accessToken ?? data.token;
22671
+ if (!newToken) {
22672
+ return Err(new Error(data.message ?? `Token renewal failed \u2014 no token in response: ${JSON.stringify(data)}`));
22673
+ }
22674
+ const twentyFourHoursMs = 24 * 60 * 60 * 1000;
22675
+ const expiresAt = data.expiryTime ? new Date(data.expiryTime).getTime() : Date.now() + twentyFourHoursMs;
22676
+ const renewed = {
22677
+ ...session,
22678
+ accessToken: newToken,
22679
+ expiresAt,
22680
+ ttlSeconds: Math.floor((expiresAt - Date.now()) / 1000)
22681
+ };
22682
+ return Ok(renewed);
22683
+ } catch (err) {
22684
+ return Err(err instanceof Error ? err : new Error(String(err)));
22685
+ }
22231
22686
  }
22232
22687
  function isSessionValid3(session) {
22233
22688
  if (!session)
@@ -22256,10 +22711,10 @@ function safeSide3(raw) {
22256
22711
  }
22257
22712
  function parseTimestamp3(ts) {
22258
22713
  if (!ts)
22259
- return Date.now();
22714
+ return 0;
22260
22715
  if (typeof ts === "number")
22261
22716
  return ts > 1000000000000 ? ts : ts * 1000;
22262
- return new Date(ts).getTime() || Date.now();
22717
+ return new Date(ts).getTime() || 0;
22263
22718
  }
22264
22719
  function inferInstrumentType3(exchangeSegment, tradingSymbol) {
22265
22720
  if (exchangeSegment === "NSE_EQ" || exchangeSegment === "BSE_EQ")
@@ -22368,6 +22823,7 @@ function fromTick3(tick, symbolLookup) {
22368
22823
  function fromHolding3(holding, ltp = 0) {
22369
22824
  const qty = holding.totalQty ?? holding.availableQty;
22370
22825
  const avgPrice = holding.avgCostPrice;
22826
+ ltp = holding.lastTradedPrice ?? ltp;
22371
22827
  const pnl = (ltp - avgPrice) * qty;
22372
22828
  return {
22373
22829
  broker: "dhan",
@@ -22416,13 +22872,14 @@ function nextSubId4() {
22416
22872
  return `dsub_${subIdCounter4}`;
22417
22873
  }
22418
22874
  var EXCH_SEG_FROM_BYTE = {
22419
- 0: "IDX",
22875
+ 0: "IDX_I",
22420
22876
  1: "NSE_EQ",
22421
22877
  2: "NSE_FNO",
22422
22878
  3: "NSE_CURRENCY",
22423
22879
  4: "BSE_EQ",
22424
- 5: "BSE_FNO",
22425
- 6: "MCX_COMM"
22880
+ 5: "MCX_COMM",
22881
+ 7: "BSE_CURRENCY",
22882
+ 8: "BSE_FNO"
22426
22883
  };
22427
22884
  function splitToken(token) {
22428
22885
  const sep = token.includes("|") ? "|" : ":";
@@ -22460,6 +22917,7 @@ class DhanSocket {
22460
22917
  return;
22461
22918
  }
22462
22919
  this.ws.on("open", () => {
22920
+ console.log(`[DhanSocket] WS connected for clientId=${this.clientId}`);
22463
22921
  this.state = "CONNECTED";
22464
22922
  this.reconnectAttempts = 0;
22465
22923
  this.startHeartbeat();
@@ -22469,6 +22927,11 @@ class DhanSocket {
22469
22927
  });
22470
22928
  this.ws.on("message", (raw, isBinary) => {
22471
22929
  try {
22930
+ if (!isBinary) {
22931
+ console.log(`[DhanSocket] WS JSON msg: ${raw.toString().slice(0, 200)}`);
22932
+ } else {
22933
+ console.log(`[DhanSocket] WS binary msg: ${Buffer.isBuffer(raw) ? raw.length : "?"} bytes`);
22934
+ }
22472
22935
  if (isBinary) {
22473
22936
  const buf = raw instanceof ArrayBuffer ? Buffer.from(raw) : Buffer.isBuffer(raw) ? raw : Array.isArray(raw) ? Buffer.concat(raw) : Buffer.from(raw);
22474
22937
  this.handleBinaryMessage(buf);
@@ -22478,12 +22941,20 @@ class DhanSocket {
22478
22941
  }
22479
22942
  } catch {}
22480
22943
  });
22481
- this.ws.on("close", () => {
22944
+ this.ws.on("close", (code, reason) => {
22945
+ console.warn(`[DhanSocket] WS closed: code=${code}, reason=${reason.toString()}`);
22482
22946
  this.state = "DISCONNECTED";
22483
22947
  this.stopHeartbeat();
22484
22948
  this.attemptReconnect();
22485
22949
  });
22486
- this.ws.on("error", () => {});
22950
+ this.ws.on("error", (err) => {
22951
+ console.error(`[DhanSocket] WS error: ${err.message}`, {
22952
+ code: err.code,
22953
+ type: err.type,
22954
+ stack: err.stack?.split(`
22955
+ `).slice(0, 3).join(" | ")
22956
+ });
22957
+ });
22487
22958
  }
22488
22959
  disconnect() {
22489
22960
  this.clearReconnectTimer();
@@ -22549,7 +23020,33 @@ class DhanSocket {
22549
23020
  const securityId = data.readInt32LE(4);
22550
23021
  const exchangeSegment = EXCH_SEG_FROM_BYTE[exchSegByte] ?? "NSE_EQ";
22551
23022
  const fullKey = `${exchangeSegment}|${securityId}`;
22552
- if (responseCode === 2 && data.length >= 16) {
23023
+ if (responseCode === 1 && data.length >= 32) {
23024
+ const ltp = data.readFloatLE(8);
23025
+ const ltt = data.readInt32LE(12);
23026
+ const open = data.readFloatLE(16);
23027
+ const close = data.readFloatLE(20);
23028
+ const high = data.readFloatLE(24);
23029
+ const low = data.readFloatLE(28);
23030
+ const tick = {
23031
+ securityId,
23032
+ LTP: ltp,
23033
+ open,
23034
+ high,
23035
+ low,
23036
+ close,
23037
+ volume: 0,
23038
+ bestBidPrice: 0,
23039
+ bestAskPrice: 0,
23040
+ bestBidQty: 0,
23041
+ bestAskQty: 0,
23042
+ exchangeSegment,
23043
+ timestamp: ltt > 0 ? ltt * 1000 : Date.now()
23044
+ };
23045
+ const symbol = this.tokenToSymbol.get(fullKey) ?? this.tokenToSymbol.get(String(securityId));
23046
+ const unified = fromTick3(tick, symbol);
23047
+ unified.token = fullKey;
23048
+ this.dispatchTick(unified);
23049
+ } else if (responseCode === 2 && data.length >= 16) {
22553
23050
  const ltp = data.readFloatLE(8);
22554
23051
  const ltt = data.readInt32LE(12);
22555
23052
  const tick = {
@@ -22605,7 +23102,7 @@ class DhanSocket {
22605
23102
  handleJsonMessage(data) {
22606
23103
  if (data["type"] === "ticker_data") {
22607
23104
  const tick = data;
22608
- const fullKey = tick.exchangeSegment ? `${tick.exchangeSegment}|${tick.securityId}` : String(tick.securityId);
23105
+ const fullKey = tick.exchangeSegment ? `${tick.exchangeSegment}:${tick.securityId}` : String(tick.securityId);
22609
23106
  const symbol = this.tokenToSymbol.get(fullKey) ?? this.tokenToSymbol.get(String(tick.securityId));
22610
23107
  const unified = fromTick3(tick, symbol);
22611
23108
  unified.token = fullKey;
@@ -22627,7 +23124,9 @@ class DhanSocket {
22627
23124
  InstrumentCount: instruments.length,
22628
23125
  InstrumentList: instruments
22629
23126
  };
22630
- this.ws.send(JSON.stringify(request));
23127
+ const msg = JSON.stringify(request);
23128
+ console.log(`[DhanSocket] Sending subscribe: ${msg}`);
23129
+ this.ws.send(msg);
22631
23130
  }
22632
23131
  sendUnsubscribe(tokens) {
22633
23132
  if (!this.ws || this.state !== "CONNECTED")
@@ -22702,6 +23201,15 @@ var DHAN_EXCHANGE_MAP = {
22702
23201
  NFO: "NFO",
22703
23202
  MCX: "MCX"
22704
23203
  };
23204
+ function resolveDhanExchange(exchId, segment) {
23205
+ if (segment === "D")
23206
+ return exchId === "BSE" ? "BFO" : "NFO";
23207
+ if (segment === "C")
23208
+ return "CDS";
23209
+ if (segment === "M")
23210
+ return "MCX";
23211
+ return exchId === "BSE" ? "BSE" : "NSE";
23212
+ }
22705
23213
  function mapInstrumentType3(instName, optionType) {
22706
23214
  const opt = optionType?.toUpperCase();
22707
23215
  if (opt === "CE")
@@ -22709,7 +23217,9 @@ function mapInstrumentType3(instName, optionType) {
22709
23217
  if (opt === "PE")
22710
23218
  return "PE";
22711
23219
  const inst = instName?.toUpperCase();
22712
- if (inst?.includes("EQUITY") || inst === "INDEX" || inst === "EQ")
23220
+ if (inst === "INDEX")
23221
+ return "IDX";
23222
+ if (inst?.includes("EQUITY") || inst === "EQ")
22713
23223
  return "EQ";
22714
23224
  if (inst?.includes("FUT"))
22715
23225
  return "FUT";
@@ -22843,14 +23353,16 @@ class DhanInstruments {
22843
23353
  const securityId = fields["SEM_SMST_SECURITY_ID"] ?? fields["securityId"] ?? "";
22844
23354
  const tradingSymbol = fields["SEM_TRADING_SYMBOL"] ?? fields["tradingSymbol"] ?? "";
22845
23355
  const customSymbol = fields["SEM_CUSTOM_SYMBOL"] ?? fields["customSymbol"] ?? tradingSymbol;
22846
- const exchangeRaw = fields["SEM_EXM_EXCH_ID"] ?? fields["SEM_SEGMENT"] ?? fields["exchangeSegment"] ?? "NSE_EQ";
23356
+ const exchId = fields["SEM_EXM_EXCH_ID"] ?? "";
23357
+ const segment = fields["SEM_SEGMENT"] ?? "";
23358
+ const exchangeSegment = fields["exchangeSegment"] ?? "";
22847
23359
  const instNameRaw = fields["SEM_INSTRUMENT_NAME"] ?? fields["instrumentType"] ?? "EQUITY";
22848
23360
  const optionType = fields["SEM_OPTION_TYPE"] ?? fields["optionType"] ?? "";
22849
23361
  const lotSizeRaw = fields["SEM_LOT_UNITS"] ?? fields["lotSize"] ?? "1";
22850
23362
  const tickSizeRaw = fields["SEM_TICK_SIZE"] ?? fields["tickSize"] ?? "0.05";
22851
23363
  const expiryRaw = fields["SEM_EXPIRY_DATE"] ?? fields["expiryDate"] ?? "";
22852
23364
  const strikeRaw = fields["SEM_STRIKE_PRICE"] ?? fields["strikePrice"] ?? "";
22853
- const exchange = DHAN_EXCHANGE_MAP[exchangeRaw] ?? EXCHANGE_FROM_DHAN[exchangeRaw] ?? "NSE";
23365
+ const exchange = exchId && segment ? resolveDhanExchange(exchId, segment) : DHAN_EXCHANGE_MAP[exchangeSegment] ?? EXCHANGE_FROM_DHAN[exchangeSegment] ?? "NSE";
22854
23366
  const instrumentType = mapInstrumentType3(instNameRaw, optionType);
22855
23367
  return {
22856
23368
  tradingSymbol,
@@ -22907,6 +23419,7 @@ class DhanBroker {
22907
23419
  session = null;
22908
23420
  socket = null;
22909
23421
  clientId = "";
23422
+ oauthCreds = null;
22910
23423
  instrumentsImpl;
22911
23424
  _iMaster;
22912
23425
  constructor(options) {
@@ -22924,7 +23437,14 @@ class DhanBroker {
22924
23437
  return result;
22925
23438
  }
22926
23439
  async refreshSession(session) {
22927
- return refreshSession3(session);
23440
+ const result = await refreshSession3(session);
23441
+ if (result.ok) {
23442
+ this.session = result.value;
23443
+ if (!this.clientId && result.value.userId)
23444
+ this.clientId = result.value.userId;
23445
+ this.instrumentsImpl.setCredentials(this.clientId, result.value.accessToken);
23446
+ }
23447
+ return result;
22928
23448
  }
22929
23449
  isSessionValid() {
22930
23450
  return isSessionValid3(this.session);
@@ -22934,6 +23454,27 @@ class DhanBroker {
22934
23454
  return null;
22935
23455
  return new Date(this.session.expiresAt);
22936
23456
  }
23457
+ getSession() {
23458
+ return this.session;
23459
+ }
23460
+ async generateConsent(creds) {
23461
+ this.oauthCreds = creds;
23462
+ this.clientId = creds.userId ?? "";
23463
+ return generateConsent(creds);
23464
+ }
23465
+ async consumeConsent(tokenId) {
23466
+ if (!this.oauthCreds) {
23467
+ return Err(new Error("Call generateConsent() before consumeConsent()"));
23468
+ }
23469
+ const result = await consumeConsent(this.oauthCreds, tokenId);
23470
+ if (!result.ok)
23471
+ return result;
23472
+ this.session = result.value;
23473
+ this.clientId = this.oauthCreds.userId ?? "";
23474
+ this.instrumentsImpl.setCredentials(this.clientId, this.session.accessToken);
23475
+ this.oauthCreds = null;
23476
+ return result;
23477
+ }
22937
23478
  async sync(force) {
22938
23479
  return this._iMaster.syncBroker(this.id, this.instruments, force);
22939
23480
  }
@@ -22953,9 +23494,11 @@ class DhanBroker {
22953
23494
  if (!asyncResolved.ok) {
22954
23495
  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
23496
  }
22956
- resolvedParams = { ...params, exchange: asyncResolved.value.exchange };
23497
+ const nativeId = asyncResolved.value.brokerTokens.dhan?.securityId ?? params.tradingSymbol;
23498
+ resolvedParams = { ...params, exchange: asyncResolved.value.exchange, tradingSymbol: nativeId };
22957
23499
  } else {
22958
- resolvedParams = { ...params, exchange: resolved.value.exchange };
23500
+ const nativeId = resolved.value.brokerTokens.dhan?.securityId ?? params.tradingSymbol;
23501
+ resolvedParams = { ...params, exchange: resolved.value.exchange, tradingSymbol: nativeId };
22959
23502
  }
22960
23503
  }
22961
23504
  const dhanParams = toOrderParams3(resolvedParams, this.clientId);
@@ -23192,10 +23735,31 @@ class DhanBroker {
23192
23735
  return Err(err instanceof Error ? err : new Error(String(err)));
23193
23736
  }
23194
23737
  }
23738
+ toBrokerToken(instrument, format = "ws") {
23739
+ const sid = instrument.brokerTokens.dhan?.securityId;
23740
+ if (!sid)
23741
+ return null;
23742
+ const seg = instrument.instrumentType === "IDX" ? "IDX_I" : EXCHANGE_TO_DHAN[instrument.exchange] ?? "NSE_EQ";
23743
+ const sep = format === "rest" ? ":" : "|";
23744
+ return `${seg}${sep}${sid}`;
23745
+ }
23746
+ getIndexTokens(format = "ws") {
23747
+ const result = new Map;
23748
+ for (const inst of this._iMaster.getIndices()) {
23749
+ const token = this.toBrokerToken(inst, format);
23750
+ if (token)
23751
+ result.set(inst.underlying.toUpperCase(), token);
23752
+ }
23753
+ return result;
23754
+ }
23195
23755
  subscribeTicks(tokens, cb) {
23196
23756
  this.ensureSocket();
23197
23757
  return this.socket.subscribe(tokens, cb);
23198
23758
  }
23759
+ setSymbolMap(map) {
23760
+ this.ensureSocket();
23761
+ this.socket.setSymbolMap(map);
23762
+ }
23199
23763
  unsubscribeTicks(sub) {
23200
23764
  if (this.socket) {
23201
23765
  this.socket.unsubscribe(sub);
@@ -23207,7 +23771,8 @@ class DhanBroker {
23207
23771
  async getMargins() {
23208
23772
  try {
23209
23773
  const raw = await this.request("GET", "/fundlimit");
23210
- const available = Number(raw["availableBalance"] ?? raw["sodLimit"] ?? 0);
23774
+ console.log("Dhan getMargins - raw response:", raw);
23775
+ const available = Number(raw["availabelBalance"] ?? raw["availabelBalance"] ?? 0);
23211
23776
  const used = Number(raw["utilizedAmount"] ?? raw["blockedMargin"] ?? 0);
23212
23777
  const collateral = Number(raw["collateralAmount"] ?? 0);
23213
23778
  return Ok({
@@ -23226,6 +23791,7 @@ class DhanBroker {
23226
23791
  async getFunds() {
23227
23792
  try {
23228
23793
  const margins = await this.getMargins();
23794
+ console.log("Dhan getFunds - margins:", margins);
23229
23795
  if (!margins.ok)
23230
23796
  return margins;
23231
23797
  return Ok({
@@ -23687,6 +24253,9 @@ class PaperBroker {
23687
24253
  return null;
23688
24254
  return new Date(this.session.expiresAt);
23689
24255
  }
24256
+ getSession() {
24257
+ return this.session;
24258
+ }
23690
24259
  async placeOrder(params) {
23691
24260
  try {
23692
24261
  if (params.side === "BUY") {
@@ -23818,6 +24387,12 @@ class PaperBroker {
23818
24387
  }
23819
24388
  return Err(new Error("Paper broker requires a data source for candles. Call setDataSource()."));
23820
24389
  }
24390
+ toBrokerToken(_instrument, _format = "ws") {
24391
+ return null;
24392
+ }
24393
+ getIndexTokens(_format = "ws") {
24394
+ return new Map;
24395
+ }
23821
24396
  subscribeTicks(tokens, cb) {
23822
24397
  this.subIdCounter += 1;
23823
24398
  const subId = `paper_sub_${this.subIdCounter}`;
@@ -24009,8 +24584,1298 @@ class PaperBroker {
24009
24584
  };
24010
24585
  }
24011
24586
  }
24587
+ // src/brokers/fivepaisa/fivepaisa-auth.ts
24588
+ import crypto3 from "crypto";
24589
+
24590
+ // src/brokers/fivepaisa/fivepaisa-constants.ts
24591
+ var FIVEPAISA_API_BASE = "https://Openapi.5paisa.com/VendorsAPI/Service1.svc";
24592
+ var FIVEPAISA_HISTORICAL_BASE = "https://openapi.5paisa.com/V2/historical";
24593
+ var FIVEPAISA_SCRIP_MASTER_BASE = "https://openapi.5paisa.com/VendorsAPI/Service1.svc/ScripMaster/segment";
24594
+ var ROUTES = {
24595
+ TOTP_LOGIN: `${FIVEPAISA_API_BASE}/TOTPLogin`,
24596
+ ACCESS_TOKEN: `${FIVEPAISA_API_BASE}/GetAccessToken`,
24597
+ MARGIN: `${FIVEPAISA_API_BASE}/V4/Margin`,
24598
+ ORDER_BOOK: `${FIVEPAISA_API_BASE}/V3/OrderBook`,
24599
+ HOLDINGS: `${FIVEPAISA_API_BASE}/V3/Holding`,
24600
+ POSITIONS: `${FIVEPAISA_API_BASE}/V1/NetPositionNetWise`,
24601
+ PLACE_ORDER: `${FIVEPAISA_API_BASE}/V1/PlaceOrderRequest`,
24602
+ MODIFY_ORDER: `${FIVEPAISA_API_BASE}/V1/ModifyOrderRequest`,
24603
+ CANCEL_ORDER: `${FIVEPAISA_API_BASE}/V1/CancelOrderRequest`,
24604
+ ORDER_STATUS: `${FIVEPAISA_API_BASE}/V2/OrderStatus`,
24605
+ TRADE_BOOK: `${FIVEPAISA_API_BASE}/V1/TradeBook`,
24606
+ TRADE_INFO: `${FIVEPAISA_API_BASE}/V1/TradeInformation`,
24607
+ MARKET_FEED: `${FIVEPAISA_API_BASE}/MarketFeed`,
24608
+ MARKET_FEED_V1: `${FIVEPAISA_API_BASE}/V1/MarketFeed`,
24609
+ MARKET_DEPTH: `${FIVEPAISA_API_BASE}/MarketDepth`
24610
+ };
24611
+ var REQUEST_CODES = {
24612
+ MARGIN: "5PMarginV3",
24613
+ ORDER_BOOK: "5POrdBkV2",
24614
+ HOLDINGS: "5PHoldingV2",
24615
+ POSITIONS: "5PNPNWV1",
24616
+ PLACE_ORDER: "5PPlaceOrdReq",
24617
+ MODIFY_ORDER: "5PModifyOrdReq",
24618
+ CANCEL_ORDER: "5PCancelOrdReq",
24619
+ ORDER_STATUS: "5POrdStatus",
24620
+ TRADE_BOOK: "5PTrdBkV1",
24621
+ TRADE_INFO: "5PTrdInfo",
24622
+ MARKET_FEED: "5PMF",
24623
+ MARKET_DEPTH: "5PMD"
24624
+ };
24625
+ var EXCHANGE_TO_5P = {
24626
+ NSE: "N",
24627
+ BSE: "B",
24628
+ NFO: "N",
24629
+ BFO: "B",
24630
+ MCX: "M",
24631
+ CDS: "N"
24632
+ };
24633
+ var EXCHANGE_FROM_5P = {
24634
+ N: "NSE",
24635
+ B: "BSE",
24636
+ M: "MCX"
24637
+ };
24638
+ var EXCHANGE_TYPE_MAP = {
24639
+ NSE: "C",
24640
+ BSE: "C",
24641
+ NFO: "D",
24642
+ BFO: "D",
24643
+ MCX: "D",
24644
+ CDS: "U"
24645
+ };
24646
+ var PRODUCT_TO_5P = {
24647
+ INTRADAY: true,
24648
+ DELIVERY: false,
24649
+ NORMAL: false
24650
+ };
24651
+ var PRODUCT_FROM_5P = (isIntraday) => isIntraday ? "INTRADAY" : "DELIVERY";
24652
+ var ORDER_TYPE_FROM_5P = (isStopLoss, price) => {
24653
+ if (isStopLoss)
24654
+ return "SL";
24655
+ if (price === 0)
24656
+ return "MARKET";
24657
+ return "LIMIT";
24658
+ };
24659
+ var STATUS_FROM_5P = {
24660
+ Pending: "PENDING",
24661
+ "Fully Executed": "FILLED",
24662
+ Cancelled: "CANCELLED",
24663
+ Rejected: "REJECTED",
24664
+ Modified: "OPEN",
24665
+ "After Market Order Req Received": "PENDING",
24666
+ Traded: "FILLED",
24667
+ Open: "OPEN",
24668
+ "Partially Executed": "OPEN",
24669
+ pending: "PENDING",
24670
+ "fully executed": "FILLED",
24671
+ cancelled: "CANCELLED",
24672
+ rejected: "REJECTED",
24673
+ Reject: "REJECTED",
24674
+ traded: "FILLED",
24675
+ open: "OPEN",
24676
+ "partially executed": "OPEN",
24677
+ Expired: "CANCELLED",
24678
+ expired: "CANCELLED"
24679
+ };
24680
+ var VALIDITY_TO_5P = {
24681
+ DAY: false,
24682
+ IOC: true
24683
+ };
24684
+ var CANDLE_INTERVAL_TO_5P = {
24685
+ "1m": "1m",
24686
+ "5m": "5m",
24687
+ "15m": "15m",
24688
+ "1h": "60m",
24689
+ "1d": "1d"
24690
+ };
24691
+
24692
+ // src/brokers/fivepaisa/fivepaisa-auth.ts
24693
+ function generateTOTP2(secret) {
24694
+ const base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
24695
+ let bits = "";
24696
+ for (const ch of secret.toUpperCase().replace(/[^A-Z2-7]/g, "")) {
24697
+ const idx = base32Chars.indexOf(ch);
24698
+ if (idx >= 0)
24699
+ bits += idx.toString(2).padStart(5, "0");
24700
+ }
24701
+ const decoded = [];
24702
+ for (let i = 0;i + 8 <= bits.length; i += 8) {
24703
+ decoded.push(parseInt(bits.slice(i, i + 8), 2));
24704
+ }
24705
+ const key = Buffer.from(decoded);
24706
+ const timeStep = Math.floor(Date.now() / 1000 / 30);
24707
+ const timeBuffer = Buffer.alloc(8);
24708
+ timeBuffer.writeUInt32BE(0, 0);
24709
+ timeBuffer.writeUInt32BE(timeStep, 4);
24710
+ const hmac = crypto3.createHmac("sha1", key).update(timeBuffer).digest();
24711
+ const offset = hmac[hmac.length - 1] & 15;
24712
+ const code = (hmac[offset] & 127) << 24 | (hmac[offset + 1] & 255) << 16 | (hmac[offset + 2] & 255) << 8 | hmac[offset + 3] & 255;
24713
+ return String(code % 1e6).padStart(6, "0");
24714
+ }
24715
+ async function authenticate4(creds) {
24716
+ const userKey = creds.userKey ?? creds.apiKey;
24717
+ const clientCode = creds.clientCode;
24718
+ const encryptionKey = creds.encryptionKey;
24719
+ const userId = creds.userId ?? "";
24720
+ const pin = creds.pin;
24721
+ if (!userKey)
24722
+ return Err(new Error("Missing userKey/apiKey in credentials"));
24723
+ if (!clientCode)
24724
+ return Err(new Error("Missing clientCode in credentials"));
24725
+ if (!pin)
24726
+ return Err(new Error("Missing pin in credentials"));
24727
+ if (!encryptionKey)
24728
+ return Err(new Error("Missing encryptionKey in credentials"));
24729
+ const totpSecret = creds.totpSecret;
24730
+ const totpCode = totpSecret ? generateTOTP2(totpSecret) : creds.totp;
24731
+ if (!totpCode) {
24732
+ return Err(new Error("Missing totpSecret or totp (6-digit code) in credentials"));
24733
+ }
24734
+ try {
24735
+ const totpPayload = {
24736
+ head: { Key: userKey },
24737
+ body: { Email_ID: clientCode, TOTP: totpCode, PIN: pin }
24738
+ };
24739
+ console.log("[5paisa] Step 1: TOTPLogin...");
24740
+ const totpRes = await fetch(ROUTES.TOTP_LOGIN, {
24741
+ method: "POST",
24742
+ headers: { "Content-Type": "application/json" },
24743
+ body: JSON.stringify(totpPayload)
24744
+ });
24745
+ let totpJson = await totpRes.json();
24746
+ if (!totpJson.body?.RequestToken && totpSecret && totpJson.body?.Message?.toLowerCase().includes("used in past")) {
24747
+ const secondsIntoStep = Math.floor(Date.now() / 1000) % 30;
24748
+ const waitMs = (30 - secondsIntoStep + 1) * 1000;
24749
+ console.log(`[5paisa] TOTP already used \u2014 waiting ${Math.ceil(waitMs / 1000)}s for next window...`);
24750
+ await new Promise((r) => setTimeout(r, waitMs));
24751
+ const freshTotp = generateTOTP2(totpSecret);
24752
+ const retryRes = await fetch(ROUTES.TOTP_LOGIN, {
24753
+ method: "POST",
24754
+ headers: { "Content-Type": "application/json" },
24755
+ body: JSON.stringify({ head: { Key: userKey }, body: { Email_ID: clientCode, TOTP: freshTotp, PIN: pin } })
24756
+ });
24757
+ totpJson = await retryRes.json();
24758
+ }
24759
+ if (!totpJson.body?.RequestToken) {
24760
+ return Err(new Error(`5paisa TOTP login failed: ${totpJson.body?.Message ?? "No RequestToken returned"}`));
24761
+ }
24762
+ console.log("[5paisa] Step 1 OK: got RequestToken");
24763
+ return exchangeRequestToken(totpJson.body.RequestToken, userKey, encryptionKey, userId, clientCode);
24764
+ } catch (err) {
24765
+ return Err(err instanceof Error ? err : new Error(String(err)));
24766
+ }
24767
+ }
24768
+ async function exchangeRequestToken(requestToken, userKey, encryptionKey, userId, clientCode) {
24769
+ try {
24770
+ const accessPayload = {
24771
+ head: { Key: userKey },
24772
+ body: {
24773
+ RequestToken: requestToken,
24774
+ EncryKey: encryptionKey,
24775
+ UserId: userId
24776
+ }
24777
+ };
24778
+ console.log("[5paisa] Step 2: GetAccessToken...");
24779
+ const accessRes = await fetch(ROUTES.ACCESS_TOKEN, {
24780
+ method: "POST",
24781
+ headers: { "Content-Type": "application/json" },
24782
+ body: JSON.stringify(accessPayload)
24783
+ });
24784
+ const accessJson = await accessRes.json();
24785
+ if (!accessJson.body?.AccessToken) {
24786
+ return Err(new Error(`5paisa access token failed: ${accessJson.body?.Message ?? "No AccessToken returned"}`));
24787
+ }
24788
+ const resolvedClientCode = accessJson.body.ClientCode || clientCode;
24789
+ console.log(`[5paisa] Step 2 OK: authenticated as ${resolvedClientCode}`);
24790
+ const session = {
24791
+ accessToken: accessJson.body.AccessToken,
24792
+ refreshToken: undefined,
24793
+ expiresAt: getEndOfDay3(),
24794
+ ttlSeconds: secondsUntilEndOfDay3(),
24795
+ brokerId: "fivepaisa",
24796
+ userId: resolvedClientCode,
24797
+ metadata: { userKey, requestToken }
24798
+ };
24799
+ return Ok(session);
24800
+ } catch (err) {
24801
+ return Err(err instanceof Error ? err : new Error(String(err)));
24802
+ }
24803
+ }
24804
+ async function authenticateWithToken(creds) {
24805
+ const accessToken = creds.accessToken;
24806
+ const clientCode = creds.clientCode ?? creds.userId ?? "";
24807
+ if (!accessToken)
24808
+ return Err(new Error("Missing accessToken in credentials"));
24809
+ return Ok({
24810
+ accessToken,
24811
+ refreshToken: undefined,
24812
+ expiresAt: getEndOfDay3(),
24813
+ ttlSeconds: secondsUntilEndOfDay3(),
24814
+ brokerId: "fivepaisa",
24815
+ userId: clientCode,
24816
+ metadata: { userKey: creds.userKey ?? creds.apiKey }
24817
+ });
24818
+ }
24819
+ async function refreshSession4(_session) {
24820
+ return Err(new Error("5paisa does not support session refresh. Re-authenticate with TOTP."));
24821
+ }
24822
+ function isSessionValid4(session) {
24823
+ if (!session)
24824
+ return false;
24825
+ return Date.now() < session.expiresAt;
24826
+ }
24827
+ function getEndOfDay3() {
24828
+ const now = new Date;
24829
+ const eod = new Date(now);
24830
+ eod.setHours(23, 59, 59, 999);
24831
+ return eod.getTime();
24832
+ }
24833
+ function secondsUntilEndOfDay3() {
24834
+ return Math.max(0, Math.floor((getEndOfDay3() - Date.now()) / 1000));
24835
+ }
24836
+
24837
+ // src/brokers/fivepaisa/fivepaisa-mapper.ts
24838
+ function num2(val, fallback = 0) {
24839
+ if (val === undefined || val === null || val === "")
24840
+ return fallback;
24841
+ const n = Number(val);
24842
+ return isNaN(n) ? fallback : n;
24843
+ }
24844
+ function safeExchange4(exch, exchType) {
24845
+ if (exchType === "D") {
24846
+ return exch === "B" ? "BFO" : "NFO";
24847
+ }
24848
+ if (exchType === "U")
24849
+ return "CDS";
24850
+ return EXCHANGE_FROM_5P[exch] ?? "NSE";
24851
+ }
24852
+ function inferInstrumentType4(exchType, symbol) {
24853
+ if (exchType === "C")
24854
+ return "EQ";
24855
+ if (symbol.endsWith("CE"))
24856
+ return "CE";
24857
+ if (symbol.endsWith("PE"))
24858
+ return "PE";
24859
+ return "FUT";
24860
+ }
24861
+ function inferSide4(qty) {
24862
+ if (qty > 0)
24863
+ return "LONG";
24864
+ if (qty < 0)
24865
+ return "SHORT";
24866
+ return "FLAT";
24867
+ }
24868
+ function parse5pDate(dateStr) {
24869
+ if (!dateStr)
24870
+ return 0;
24871
+ const match = dateStr.match(/\/Date\((\d+)/);
24872
+ if (match)
24873
+ return parseInt(match[1], 10);
24874
+ const ts = new Date(dateStr).getTime();
24875
+ if (ts && !isNaN(ts))
24876
+ return ts;
24877
+ const cleaned = dateStr.replace(/-/g, " ").replace(/\s+/g, " ").trim();
24878
+ const ts2 = new Date(cleaned).getTime();
24879
+ if (ts2 && !isNaN(ts2))
24880
+ return ts2;
24881
+ console.warn(`[5paisa] Unparseable date: "${dateStr}"`);
24882
+ return 0;
24883
+ }
24884
+ function normalize5pSymbol(scripName) {
24885
+ if (!scripName)
24886
+ return "";
24887
+ const optMatch = scripName.match(/^(.+?)\s+(\d{1,2})\s+(\w{3})\s+(\d{4})\s+(CE|PE)\s+([\d.]+)$/i);
24888
+ if (optMatch) {
24889
+ const [, underlying, day, mon, year, optType, strike] = optMatch;
24890
+ const strikeClean = strike.replace(/\.00$/, "");
24891
+ const yy = year.slice(2);
24892
+ const monUpper = mon.toUpperCase();
24893
+ return `${underlying.trim()}-${strikeClean}-${optType.toUpperCase()}-${day.padStart(2, "0")}${monUpper}${yy}`;
24894
+ }
24895
+ const futMatch = scripName.match(/^(.+?)\s+(\d{1,2})\s+(\w{3})\s+(\d{4})\s+FUT$/i);
24896
+ if (futMatch) {
24897
+ const [, underlying, day, mon, year] = futMatch;
24898
+ const yy = year.slice(2);
24899
+ return `${underlying.trim()}-FUT-${day.padStart(2, "0")}${mon.toUpperCase()}${yy}`;
24900
+ }
24901
+ return scripName;
24902
+ }
24903
+ function safeStatus4(raw) {
24904
+ const direct = STATUS_FROM_5P[raw];
24905
+ if (direct)
24906
+ return direct;
24907
+ const lower = STATUS_FROM_5P[raw.toLowerCase()];
24908
+ if (lower)
24909
+ return lower;
24910
+ const lc = raw.toLowerCase();
24911
+ if (lc.includes("reject"))
24912
+ return "REJECTED";
24913
+ if (lc.includes("cancel") || lc.includes("expired"))
24914
+ return "CANCELLED";
24915
+ if (lc.includes("traded") || lc.includes("executed") || lc.includes("filled"))
24916
+ return "FILLED";
24917
+ if (lc.includes("open") || lc.includes("partial"))
24918
+ return "OPEN";
24919
+ console.warn(`[5paisa] Unmapped order status: "${raw}" \u2014 defaulting to PENDING`);
24920
+ return "PENDING";
24921
+ }
24922
+ function toOrderPayload(params, clientCode, appSource) {
24923
+ const isStopLoss = params.type === "SL" || params.type === "SL_M";
24924
+ const isMarket = params.type === "MARKET" || params.type === "SL_M";
24925
+ const date = new Date;
24926
+ date.setDate(date.getDate() + 1);
24927
+ return {
24928
+ ClientCode: clientCode,
24929
+ Exchange: EXCHANGE_TO_5P[params.exchange],
24930
+ ExchangeType: EXCHANGE_TYPE_MAP[params.exchange],
24931
+ Price: isMarket ? 0 : params.price ?? 0,
24932
+ OrderID: 0,
24933
+ OrderType: params.side,
24934
+ Qty: params.quantity,
24935
+ UniqueOrderID: "1",
24936
+ DisQty: params.disclosedQty ?? params.quantity,
24937
+ IsStopLossOrder: isStopLoss,
24938
+ StopLossPrice: params.triggerPrice ?? 0,
24939
+ IsIOCOrder: VALIDITY_TO_5P[params.validity],
24940
+ IsIntraday: PRODUCT_TO_5P[params.product],
24941
+ IsAHOrder: "N",
24942
+ ValidTillDate: `/Date(${date.getTime()})/`,
24943
+ AppSource: appSource,
24944
+ ScripCode: 0,
24945
+ ScripData: "",
24946
+ RemoteOrderID: params.tag ?? ""
24947
+ };
24948
+ }
24949
+ function fromOrder4(order) {
24950
+ const qty = num2(order.Qty);
24951
+ const filled = num2(order.TradedQty);
24952
+ const exchType = String(order.ExchType ?? "C");
24953
+ return {
24954
+ orderId: String(order.ExchOrderID || order.BrokerOrderId),
24955
+ brokerOrderId: String(order.BrokerOrderId),
24956
+ broker: "fivepaisa",
24957
+ tradingSymbol: normalize5pSymbol(order.ScripName ?? ""),
24958
+ exchange: safeExchange4(String(order.Exch), exchType),
24959
+ side: order.BuySell === "B" ? "BUY" : "SELL",
24960
+ type: ORDER_TYPE_FROM_5P(order.WithSL === "Y", num2(order.Rate)),
24961
+ product: PRODUCT_FROM_5P(order.DelvIntra === "I"),
24962
+ quantity: qty,
24963
+ filledQty: filled,
24964
+ pendingQty: num2(order.PendingQty),
24965
+ price: num2(order.Rate),
24966
+ triggerPrice: num2(order.SLTriggerRate) || undefined,
24967
+ averagePrice: filled > 0 ? num2(order.TradedPrice ?? order.Rate) : 0,
24968
+ status: safeStatus4(order.OrderStatus),
24969
+ validity: num2(order.OrderValidity) === 3 ? "IOC" : "DAY",
24970
+ tag: order.RemoteOrderID || undefined,
24971
+ placedAt: parse5pDate(String(order.BrokerOrderTime ?? order.OrderDateTime ?? "")),
24972
+ updatedAt: parse5pDate(String(order.ExchOrderTime ?? order.BrokerOrderTime ?? ""))
24973
+ };
24974
+ }
24975
+ function fromPosition4(pos) {
24976
+ const netQty = num2(pos.NetQty);
24977
+ const buyQty = num2(pos.BuyQty);
24978
+ const sellQty = num2(pos.SellQty);
24979
+ const ltp = num2(pos.LTP);
24980
+ const bookedPnl = num2(pos.BookedPL);
24981
+ const mtom = num2(pos.MTOM);
24982
+ const buyAvg = num2(pos.BuyAvgRate);
24983
+ const sellAvg = num2(pos.SellAvgRate);
24984
+ const avgPrice = netQty > 0 ? buyAvg : netQty < 0 ? sellAvg : 0;
24985
+ const exchType = String(pos.ExchType ?? "C");
24986
+ return {
24987
+ broker: "fivepaisa",
24988
+ tradingSymbol: normalize5pSymbol(pos.ScripName ?? pos.Symbol ?? ""),
24989
+ exchange: safeExchange4(String(pos.Exch), exchType),
24990
+ side: inferSide4(netQty),
24991
+ quantity: Math.abs(netQty),
24992
+ buyQty,
24993
+ sellQty,
24994
+ avgPrice,
24995
+ ltp,
24996
+ pnl: bookedPnl + mtom,
24997
+ realizedPnl: bookedPnl,
24998
+ unrealizedPnl: mtom,
24999
+ product: PRODUCT_FROM_5P(true),
25000
+ instrumentType: inferInstrumentType4(exchType, pos.ScripName ?? "")
25001
+ };
25002
+ }
25003
+ function fromHolding4(holding) {
25004
+ const qty = num2(holding.Quantity);
25005
+ const ltp = num2(holding.CurrentPrice);
25006
+ return {
25007
+ broker: "fivepaisa",
25008
+ tradingSymbol: holding.Symbol ?? holding.FullName ?? "",
25009
+ exchange: num2(holding.NseCode) > 0 ? "NSE" : "BSE",
25010
+ isin: "",
25011
+ quantity: qty,
25012
+ avgPrice: 0,
25013
+ ltp,
25014
+ pnl: 0,
25015
+ dayChange: 0,
25016
+ dayChangePercent: 0
25017
+ };
25018
+ }
25019
+ function fromMarketFeed(item) {
25020
+ const ltp = num2(item.LastRate);
25021
+ const close = num2(item.PClose);
25022
+ const change = ltp - close;
25023
+ const changePct = close !== 0 ? change / close * 100 : 0;
25024
+ const exchType = String(item.ExchType ?? "C");
25025
+ return {
25026
+ tradingSymbol: String(item.ScripData ?? item.Token ?? item.ScripCode),
25027
+ exchange: safeExchange4(String(item.Exch), exchType),
25028
+ ltp,
25029
+ open: num2(item.OpenRate),
25030
+ high: num2(item.High),
25031
+ low: num2(item.Low),
25032
+ close,
25033
+ volume: num2(item.TotalQty),
25034
+ oi: item.OpenInterest ? num2(item.OpenInterest) : undefined,
25035
+ bid: num2(item.BidRate),
25036
+ ask: num2(item.OffRate),
25037
+ bidQty: num2(item.BidQty),
25038
+ askQty: num2(item.OffQty),
25039
+ upperCircuit: 0,
25040
+ lowerCircuit: 0,
25041
+ change,
25042
+ changePercent: changePct,
25043
+ timestamp: parse5pDate(String(item.TickDt ?? ""))
25044
+ };
25045
+ }
25046
+ function fromTradeBookEntry(t) {
25047
+ const exchType = String(t.ExchType ?? "C");
25048
+ return {
25049
+ tradeId: t.TradeNo ?? "",
25050
+ orderId: String(t.ExchOrderID || t.BrokerOrderId),
25051
+ tradingSymbol: normalize5pSymbol(t.ScripName ?? ""),
25052
+ exchange: safeExchange4(String(t.Exch), exchType),
25053
+ side: t.BuySell === "B" ? "BUY" : "SELL",
25054
+ quantity: num2(t.Qty),
25055
+ price: num2(t.Rate),
25056
+ product: PRODUCT_FROM_5P(t.DelvIntra === "I"),
25057
+ timestamp: parse5pDate(String(t.TradeTime ?? ""))
25058
+ };
25059
+ }
25060
+
25061
+ // src/brokers/fivepaisa/fivepaisa-socket.ts
25062
+ import WS3 from "ws";
25063
+ var WS_URLS = {
25064
+ A: "wss://aopenfeed.5paisa.com/feeds/api/chat?Value1=",
25065
+ B: "wss://bopenfeed.5paisa.com/feeds/api/chat?Value1=",
25066
+ C: "wss://openfeed.5paisa.com/feeds/api/chat?Value1="
25067
+ };
25068
+ var DEFAULT_WS_URL = "wss://openfeed.5paisa.com/feeds/api/chat?Value1=";
25069
+ function decodeJwtPayload(token) {
25070
+ try {
25071
+ const parts = token.split(".");
25072
+ if (parts.length < 2)
25073
+ return {};
25074
+ const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
25075
+ const json = Buffer.from(payload, "base64").toString("utf-8");
25076
+ return JSON.parse(json);
25077
+ } catch {
25078
+ return {};
25079
+ }
25080
+ }
25081
+ function getWsUrl(accessToken) {
25082
+ const payload = decodeJwtPayload(accessToken);
25083
+ const server = String(payload["RedirectServer"] ?? "C");
25084
+ return WS_URLS[server] ?? DEFAULT_WS_URL;
25085
+ }
25086
+ var subIdCounter5 = 0;
25087
+ function nextSubId5() {
25088
+ return `5p_sub_${++subIdCounter5}`;
25089
+ }
25090
+
25091
+ class FivePaisaSocket {
25092
+ ws = null;
25093
+ state = "DISCONNECTED";
25094
+ subscriptions = new Map;
25095
+ subscribedTokens = new Set;
25096
+ tokenToSymbol = new Map;
25097
+ clientCode;
25098
+ accessToken;
25099
+ reconnectAttempts = 0;
25100
+ maxReconnects = 5;
25101
+ reconnectTimer = null;
25102
+ constructor(clientCode, accessToken) {
25103
+ this.clientCode = clientCode;
25104
+ this.accessToken = accessToken;
25105
+ }
25106
+ connect() {
25107
+ if (this.state === "CONNECTED" || this.state === "CONNECTING")
25108
+ return;
25109
+ this.state = "CONNECTING";
25110
+ const baseUrl = getWsUrl(this.accessToken);
25111
+ const url = `${baseUrl}${this.accessToken}|${this.clientCode}`;
25112
+ this.ws = new WS3(url);
25113
+ this.ws.on("open", () => {
25114
+ this.state = "CONNECTED";
25115
+ this.reconnectAttempts = 0;
25116
+ if (this.subscribedTokens.size > 0) {
25117
+ this.sendSubscription("Subscribe", Array.from(this.subscribedTokens));
25118
+ }
25119
+ });
25120
+ this.ws.on("message", (data) => {
25121
+ this.handleMessage(data);
25122
+ });
25123
+ this.ws.on("close", () => {
25124
+ this.state = "DISCONNECTED";
25125
+ this.scheduleReconnect();
25126
+ });
25127
+ this.ws.on("error", (err) => {
25128
+ console.warn("[FivePaisaSocket] WS error:", err.message);
25129
+ });
25130
+ }
25131
+ disconnect() {
25132
+ this.state = "DISCONNECTED";
25133
+ this.reconnectAttempts = this.maxReconnects;
25134
+ if (this.reconnectTimer) {
25135
+ clearTimeout(this.reconnectTimer);
25136
+ this.reconnectTimer = null;
25137
+ }
25138
+ if (this.ws) {
25139
+ this.ws.close();
25140
+ this.ws = null;
25141
+ }
25142
+ }
25143
+ reconnect(newToken) {
25144
+ if (newToken)
25145
+ this.accessToken = newToken;
25146
+ this.reconnectAttempts = 0;
25147
+ if (this.ws) {
25148
+ this.ws.close();
25149
+ this.ws = null;
25150
+ }
25151
+ this.connect();
25152
+ }
25153
+ subscribe(tokens, callback) {
25154
+ const id = nextSubId5();
25155
+ const sub = { id, tokens, callback };
25156
+ this.subscriptions.set(id, sub);
25157
+ const newTokens = [];
25158
+ for (const t of tokens) {
25159
+ if (!this.subscribedTokens.has(t)) {
25160
+ this.subscribedTokens.add(t);
25161
+ newTokens.push(t);
25162
+ }
25163
+ }
25164
+ if (this.state === "DISCONNECTED") {
25165
+ this.connect();
25166
+ } else if (newTokens.length > 0 && this.state === "CONNECTED") {
25167
+ this.sendSubscription("Subscribe", newTokens);
25168
+ }
25169
+ return sub;
25170
+ }
25171
+ unsubscribe(sub) {
25172
+ this.subscriptions.delete(sub.id);
25173
+ const stillNeeded = new Set;
25174
+ for (const [, s] of this.subscriptions) {
25175
+ for (const t of s.tokens)
25176
+ stillNeeded.add(t);
25177
+ }
25178
+ const toRemove = sub.tokens.filter((t) => !stillNeeded.has(t));
25179
+ for (const t of toRemove)
25180
+ this.subscribedTokens.delete(t);
25181
+ if (toRemove.length > 0 && this.state === "CONNECTED") {
25182
+ this.sendSubscription("Unsubscribe", toRemove);
25183
+ }
25184
+ if (this.subscriptions.size === 0) {
25185
+ this.disconnect();
25186
+ }
25187
+ }
25188
+ setSymbolMap(map) {
25189
+ for (const [k, v] of map) {
25190
+ this.tokenToSymbol.set(k, v);
25191
+ }
25192
+ }
25193
+ getConnectionState() {
25194
+ return this.state;
25195
+ }
25196
+ sendSubscription(operation, tokens) {
25197
+ if (!this.ws || this.ws.readyState !== WS3.OPEN)
25198
+ return;
25199
+ const feedData = tokens.map((token) => {
25200
+ const parts = token.split(":");
25201
+ if (parts.length === 3) {
25202
+ return { Exch: parts[0], ExchType: parts[1], ScripCode: parseInt(parts[2], 10) };
25203
+ }
25204
+ if (parts.length === 2) {
25205
+ return { Exch: parts[0], ExchType: "C", ScripCode: parseInt(parts[1], 10) };
25206
+ }
25207
+ return { Exch: "N", ExchType: "C", ScripCode: parseInt(parts[0], 10) };
25208
+ });
25209
+ const payload = {
25210
+ Method: "MarketFeedV3",
25211
+ Operation: operation,
25212
+ ClientCode: this.clientCode,
25213
+ MarketFeedData: feedData
25214
+ };
25215
+ this.ws.send(JSON.stringify(payload));
25216
+ }
25217
+ handleMessage(data) {
25218
+ try {
25219
+ const raw = typeof data === "string" ? data : data.toString("utf-8");
25220
+ const parsed = JSON.parse(raw);
25221
+ const items = Array.isArray(parsed) ? parsed : [parsed];
25222
+ for (const item of items) {
25223
+ if (item.ReqType)
25224
+ continue;
25225
+ if (item.OpenInterest !== undefined && item.LastRate === undefined)
25226
+ continue;
25227
+ if (!item.Token && !item.LastRate)
25228
+ continue;
25229
+ const token = String(item.Token ?? "");
25230
+ const symbolLookup = this.tokenToSymbol.get(token);
25231
+ const tick = {
25232
+ broker: "fivepaisa",
25233
+ token,
25234
+ tradingSymbol: symbolLookup ?? item.Symbol ?? token,
25235
+ ltp: num3(item.LastRate),
25236
+ open: num3(item.OpenRate),
25237
+ high: num3(item.High),
25238
+ low: num3(item.Low),
25239
+ close: num3(item.PClose),
25240
+ volume: num3(item.TotalQty),
25241
+ oi: item.OpenInterest ? num3(item.OpenInterest) : undefined,
25242
+ bid: num3(item.BidRate),
25243
+ ask: num3(item.OffRate),
25244
+ bidQty: num3(item.BidQty),
25245
+ askQty: num3(item.OffQty),
25246
+ timestamp: Date.now()
25247
+ };
25248
+ this.dispatch(tick);
25249
+ }
25250
+ } catch {}
25251
+ }
25252
+ dispatch(tick) {
25253
+ for (const [, sub] of this.subscriptions) {
25254
+ if (sub.tokens.some((t) => t === tick.token || t.endsWith(`:${tick.token}`))) {
25255
+ sub.callback(tick);
25256
+ }
25257
+ }
25258
+ }
25259
+ scheduleReconnect() {
25260
+ if (this.reconnectAttempts >= this.maxReconnects)
25261
+ return;
25262
+ if (this.subscriptions.size === 0)
25263
+ return;
25264
+ this.reconnectAttempts++;
25265
+ this.state = "RECONNECTING";
25266
+ const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts - 1), 30000);
25267
+ this.reconnectTimer = setTimeout(() => {
25268
+ this.reconnectTimer = null;
25269
+ this.connect();
25270
+ }, delay);
25271
+ }
25272
+ }
25273
+ function num3(val) {
25274
+ if (val === undefined || val === null || val === "")
25275
+ return 0;
25276
+ const n = Number(val);
25277
+ return isNaN(n) ? 0 : n;
25278
+ }
25279
+
25280
+ // src/brokers/fivepaisa/fivepaisa-instruments.ts
25281
+ var SEGMENTS = ["nse_eq", "nse_fo", "bse_eq", "bse_fo", "mcx_fo", "nse_currency"];
25282
+ function mapExchange(exch, exchType) {
25283
+ if (exch === "M")
25284
+ return "MCX";
25285
+ if (exchType === "D")
25286
+ return exch === "B" ? "BFO" : "NFO";
25287
+ if (exchType === "U")
25288
+ return "CDS";
25289
+ if (exch === "B")
25290
+ return "BSE";
25291
+ return "NSE";
25292
+ }
25293
+ function mapInstrumentType4(scripType, expiry) {
25294
+ const upper = (scripType ?? "").toUpperCase();
25295
+ if (upper === "CE")
25296
+ return "CE";
25297
+ if (upper === "PE")
25298
+ return "PE";
25299
+ if (upper === "XX")
25300
+ return expiry ? "FUT" : "EQ";
25301
+ if (upper === "EQ" || upper === "")
25302
+ return "EQ";
25303
+ return "FUT";
25304
+ }
25305
+ function parseExpiry4(raw) {
25306
+ if (!raw || raw === "")
25307
+ return;
25308
+ const d = new Date(raw);
25309
+ return isNaN(d.getTime()) ? undefined : d;
25310
+ }
25311
+ function parseStrike4(raw) {
25312
+ const n = parseFloat(raw);
25313
+ return isNaN(n) || n === 0 ? undefined : n;
25314
+ }
25315
+
25316
+ class FivePaisaInstruments {
25317
+ capabilities = {
25318
+ bulkDump: true,
25319
+ searchAPI: false,
25320
+ rawFileCache: true,
25321
+ rawFilePath: "data/instruments/fivepaisa-instruments.csv"
25322
+ };
25323
+ accessToken = "";
25324
+ clientCode = "";
25325
+ setCredentials(clientCode, accessToken) {
25326
+ this.clientCode = clientCode;
25327
+ this.accessToken = accessToken;
25328
+ }
25329
+ async* streamDump() {
25330
+ for (const segment of SEGMENTS) {
25331
+ const url = `${FIVEPAISA_SCRIP_MASTER_BASE}/${segment}`;
25332
+ try {
25333
+ const response = await fetch(url);
25334
+ if (!response.ok)
25335
+ continue;
25336
+ const text = await response.text();
25337
+ const lines = text.split(`
25338
+ `);
25339
+ let headers = null;
25340
+ for (const line of lines) {
25341
+ const trimmed = line.trim();
25342
+ if (!trimmed)
25343
+ continue;
25344
+ const fields = trimmed.split(",");
25345
+ if (!headers) {
25346
+ headers = fields.map((h) => h.trim());
25347
+ continue;
25348
+ }
25349
+ const row = { _segment: segment };
25350
+ for (let i = 0;i < headers.length && i < fields.length; i++) {
25351
+ row[headers[i]] = fields[i]?.trim() ?? "";
25352
+ }
25353
+ yield row;
25354
+ }
25355
+ } catch {
25356
+ continue;
25357
+ }
25358
+ }
25359
+ }
25360
+ normalize(raw) {
25361
+ const f = raw;
25362
+ const exch = f["Exch"] ?? "N";
25363
+ const exchType = f["ExchType"] ?? "C";
25364
+ const scripCode = f["ScripCode"] ?? "";
25365
+ const scripData = f["ScripData"] ?? "";
25366
+ const name = f["Name"] ?? "";
25367
+ const fullName = f["FullName"] ?? "";
25368
+ const scripType = f["ScripType"] ?? "";
25369
+ const strikeRate = f["StrikeRate"] ?? "";
25370
+ const expiry = f["Expiry"] ?? "";
25371
+ const isin = f["ISIN"] ?? "";
25372
+ const lotSize = parseInt(f["LotSize"] ?? "1", 10) || 1;
25373
+ const tickSize = parseFloat(f["TickSize"] ?? "0.05") || 0.05;
25374
+ const symbolRoot = f["SymbolRoot"] ?? "";
25375
+ const exchange = mapExchange(exch, exchType);
25376
+ const isIndex = scripCode.startsWith("9999");
25377
+ const instrumentType = isIndex ? "IDX" : mapInstrumentType4(scripType, expiry);
25378
+ const tradingSymbol = scripData || name || fullName;
25379
+ const underlying = instrumentType === "EQ" ? name || symbolRoot || tradingSymbol : symbolRoot || name || tradingSymbol.replace(/\d.*/g, "").trim();
25380
+ return {
25381
+ tradingSymbol,
25382
+ exchange,
25383
+ instrumentType,
25384
+ underlying,
25385
+ expiry: parseExpiry4(expiry),
25386
+ strike: parseStrike4(strikeRate),
25387
+ lotSize,
25388
+ tickSize,
25389
+ brokerToken: scripCode,
25390
+ brokerSymbol: scripData,
25391
+ raw
25392
+ };
25393
+ }
25394
+ }
25395
+
25396
+ // src/brokers/fivepaisa/FivePaisaBroker.ts
25397
+ class FivePaisaBroker {
25398
+ id = "fivepaisa";
25399
+ name = "5paisa";
25400
+ instruments;
25401
+ has = {
25402
+ bulkDump: true,
25403
+ searchAPI: false,
25404
+ optionChain: false,
25405
+ historicalCandles: true,
25406
+ websocket: true,
25407
+ pnlReport: false
25408
+ };
25409
+ session = null;
25410
+ socket = null;
25411
+ credentials = null;
25412
+ instrumentsImpl;
25413
+ _iMaster;
25414
+ constructor(options) {
25415
+ this.instrumentsImpl = new FivePaisaInstruments;
25416
+ this.instruments = this.instrumentsImpl;
25417
+ this._iMaster = new InstrumentMaster(options);
25418
+ }
25419
+ async authenticate(creds) {
25420
+ const result = creds.accessToken ? await authenticateWithToken(creds) : await authenticate4(creds);
25421
+ if (!result.ok)
25422
+ return result;
25423
+ this.credentials = creds;
25424
+ this.session = result.value;
25425
+ this.instrumentsImpl.setCredentials(this.session.userId, this.session.accessToken);
25426
+ return result;
25427
+ }
25428
+ async refreshSession(session) {
25429
+ return refreshSession4(session);
25430
+ }
25431
+ isSessionValid() {
25432
+ return isSessionValid4(this.session);
25433
+ }
25434
+ getSessionExpiry() {
25435
+ if (!this.session)
25436
+ return null;
25437
+ return new Date(this.session.expiresAt);
25438
+ }
25439
+ getSession() {
25440
+ return this.session;
25441
+ }
25442
+ async sync(force) {
25443
+ return this._iMaster.syncBroker(this.id, this.instruments, force);
25444
+ }
25445
+ resolve(input) {
25446
+ return this._iMaster.resolve(input);
25447
+ }
25448
+ search(query, exchange, instrumentType, limit) {
25449
+ return this._iMaster.search(query, exchange, instrumentType, limit);
25450
+ }
25451
+ async placeOrder(params) {
25452
+ try {
25453
+ const exchange = params.exchange ?? "NSE";
25454
+ const clientCode = this.session?.userId ?? "";
25455
+ const appSource = parseInt(this.credentials?.appSource ?? "0", 10) || 0;
25456
+ let scripCode = 0;
25457
+ const resolveKey = `${exchange}:${params.tradingSymbol}`;
25458
+ const resolved = this.resolve(resolveKey);
25459
+ if (resolved.ok && resolved.value.brokerTokens.fivepaisa?.scripCode) {
25460
+ scripCode = parseInt(resolved.value.brokerTokens.fivepaisa.scripCode, 10);
25461
+ }
25462
+ if (scripCode === 0) {
25463
+ const results = this.search(params.tradingSymbol, exchange, undefined, 5);
25464
+ for (const r of results) {
25465
+ if (r.brokerTokens.fivepaisa?.scripCode) {
25466
+ scripCode = parseInt(r.brokerTokens.fivepaisa.scripCode, 10);
25467
+ break;
25468
+ }
25469
+ }
25470
+ }
25471
+ if (scripCode === 0) {
25472
+ return Err(new Error(`Cannot resolve ScripCode for ${params.tradingSymbol} on ${exchange}`));
25473
+ }
25474
+ const body = toOrderPayload({ ...params, exchange }, clientCode, appSource);
25475
+ body.ScripCode = scripCode;
25476
+ const payload = {
25477
+ head: { ...this.buildHead(), requestCode: REQUEST_CODES.PLACE_ORDER },
25478
+ body
25479
+ };
25480
+ const res = await this.post(ROUTES.PLACE_ORDER, payload);
25481
+ console.log("[5paisa] PlaceOrder response:", JSON.stringify(res));
25482
+ const brokerOid = res.BrokerOrderID ?? 0;
25483
+ const isRejected = (res.Status ?? 0) !== 0 || (res.RMSResponseCode ?? 0) < 0 || (res.Message ?? "").toLowerCase().includes("rejected");
25484
+ if (isRejected) {
25485
+ return Err(new Error(res.Message ?? `Order rejected (Status=${res.Status}, RMS=${res.RMSResponseCode})`));
25486
+ }
25487
+ if (brokerOid === 0) {
25488
+ return Err(new Error(`Order placement failed: ${res.Message ?? "No BrokerOrderID returned"}`));
25489
+ }
25490
+ return Ok({
25491
+ orderId: String(brokerOid),
25492
+ brokerOrderId: String(brokerOid),
25493
+ status: "PENDING",
25494
+ timestamp: Date.now()
25495
+ });
25496
+ } catch (err) {
25497
+ return Err(err instanceof Error ? err : new Error(String(err)));
25498
+ }
25499
+ }
25500
+ async modifyOrder(orderId, params) {
25501
+ try {
25502
+ const body = {
25503
+ ExchOrderID: orderId,
25504
+ Qty: params.quantity,
25505
+ Price: params.price
25506
+ };
25507
+ const payload = {
25508
+ head: { ...this.buildHead(), requestCode: REQUEST_CODES.MODIFY_ORDER },
25509
+ body: { ...this.buildBody(), ...body }
25510
+ };
25511
+ const res = await this.post(ROUTES.MODIFY_ORDER, payload);
25512
+ return Ok({
25513
+ orderId: String(res.ExchOrderID ?? orderId),
25514
+ brokerOrderId: String(res.BrokerOrderID ?? ""),
25515
+ status: "OPEN",
25516
+ timestamp: Date.now()
25517
+ });
25518
+ } catch (err) {
25519
+ return Err(err instanceof Error ? err : new Error(String(err)));
25520
+ }
25521
+ }
25522
+ async cancelOrder(orderId) {
25523
+ try {
25524
+ const payload = {
25525
+ head: { ...this.buildHead(), requestCode: REQUEST_CODES.CANCEL_ORDER },
25526
+ body: { ...this.buildBody(), ExchOrderID: orderId }
25527
+ };
25528
+ await this.post(ROUTES.CANCEL_ORDER, payload);
25529
+ return Ok({
25530
+ orderId,
25531
+ status: "CANCELLED",
25532
+ timestamp: Date.now()
25533
+ });
25534
+ } catch (err) {
25535
+ return Err(err instanceof Error ? err : new Error(String(err)));
25536
+ }
25537
+ }
25538
+ async getOrders() {
25539
+ try {
25540
+ const payload = {
25541
+ head: { ...this.buildHead(), requestCode: REQUEST_CODES.ORDER_BOOK },
25542
+ body: this.buildBody()
25543
+ };
25544
+ const res = await this.post(ROUTES.ORDER_BOOK, payload);
25545
+ if (!res.OrderBookDetail || res.OrderBookDetail.length === 0) {
25546
+ return Ok([]);
25547
+ }
25548
+ return Ok(res.OrderBookDetail.map(fromOrder4));
25549
+ } catch (err) {
25550
+ return Err(err instanceof Error ? err : new Error(String(err)));
25551
+ }
25552
+ }
25553
+ async getOrderHistory(orderId) {
25554
+ try {
25555
+ const ordersResult = await this.getOrders();
25556
+ if (!ordersResult.ok)
25557
+ return Err(ordersResult.error);
25558
+ const matching = ordersResult.value.filter((o) => o.brokerOrderId === orderId || o.orderId === orderId);
25559
+ if (matching.length === 0)
25560
+ return Ok([]);
25561
+ const order = matching[0];
25562
+ let avgPrice = order.averagePrice ?? 0;
25563
+ if (order.status === "FILLED" && avgPrice === 0) {
25564
+ try {
25565
+ const tradePayload = {
25566
+ head: { ...this.buildHead(), requestCode: REQUEST_CODES.TRADE_BOOK },
25567
+ body: this.buildBody()
25568
+ };
25569
+ const tradeRes = await this.post(ROUTES.TRADE_BOOK, tradePayload);
25570
+ const trades = tradeRes.TradeBookDetail?.filter((t) => String(t.BrokerOrderId) === orderId) ?? [];
25571
+ if (trades.length > 0) {
25572
+ const totalQty = trades.reduce((s, t) => s + (t.Qty ?? 0), 0);
25573
+ const totalVal = trades.reduce((s, t) => s + (t.Rate ?? 0) * (t.Qty ?? 0), 0);
25574
+ avgPrice = totalQty > 0 ? totalVal / totalQty : 0;
25575
+ }
25576
+ } catch {}
25577
+ }
25578
+ const updates = matching.map((o) => ({
25579
+ orderId: o.brokerOrderId || o.orderId,
25580
+ status: o.status,
25581
+ filledQty: o.filledQty ?? 0,
25582
+ averagePrice: o === order ? avgPrice : o.averagePrice ?? 0,
25583
+ timestamp: Date.now(),
25584
+ message: ""
25585
+ }));
25586
+ return Ok(updates);
25587
+ } catch (err) {
25588
+ return Err(err instanceof Error ? err : new Error(String(err)));
25589
+ }
25590
+ }
25591
+ async getPositions() {
25592
+ try {
25593
+ const payload = {
25594
+ head: { ...this.buildHead(), requestCode: REQUEST_CODES.POSITIONS },
25595
+ body: this.buildBody()
25596
+ };
25597
+ const res = await this.post(ROUTES.POSITIONS, payload);
25598
+ if (!res.NetPositionDetail || res.NetPositionDetail.length === 0) {
25599
+ return Ok([]);
25600
+ }
25601
+ return Ok(res.NetPositionDetail.map(fromPosition4));
25602
+ } catch (err) {
25603
+ return Err(err instanceof Error ? err : new Error(String(err)));
25604
+ }
25605
+ }
25606
+ async getHoldings() {
25607
+ try {
25608
+ const payload = {
25609
+ head: { ...this.buildHead(), requestCode: REQUEST_CODES.HOLDINGS },
25610
+ body: this.buildBody()
25611
+ };
25612
+ const res = await this.post(ROUTES.HOLDINGS, payload);
25613
+ if (!res.Data || res.Data.length === 0) {
25614
+ return Ok([]);
25615
+ }
25616
+ return Ok(res.Data.map(fromHolding4));
25617
+ } catch (err) {
25618
+ return Err(err instanceof Error ? err : new Error(String(err)));
25619
+ }
25620
+ }
25621
+ async getLTP(symbols) {
25622
+ try {
25623
+ const result = new Map;
25624
+ const feedData = this.parseSymbolsToFeedData(symbols);
25625
+ const payload = {
25626
+ head: { key: this.credentials?.userKey ?? this.credentials?.apiKey ?? "" },
25627
+ body: {
25628
+ MarketFeedData: feedData,
25629
+ LastRequestTime: "/Date(0)/",
25630
+ RefreshRate: "H"
25631
+ }
25632
+ };
25633
+ const res = await this.post(ROUTES.MARKET_FEED_V1, payload);
25634
+ if (res.Data && Array.isArray(res.Data)) {
25635
+ for (let i = 0;i < res.Data.length; i++) {
25636
+ result.set(symbols[i], res.Data[i].LastRate ?? 0);
25637
+ }
25638
+ }
25639
+ return Ok(result);
25640
+ } catch (err) {
25641
+ return Err(err instanceof Error ? err : new Error(String(err)));
25642
+ }
25643
+ }
25644
+ async getQuote(symbols) {
25645
+ try {
25646
+ const result = new Map;
25647
+ const feedData = this.parseSymbolsToFeedData(symbols);
25648
+ const payload = {
25649
+ head: { key: this.credentials?.userKey ?? this.credentials?.apiKey ?? "" },
25650
+ body: {
25651
+ MarketFeedData: feedData,
25652
+ LastRequestTime: "/Date(0)/",
25653
+ RefreshRate: "H"
25654
+ }
25655
+ };
25656
+ const res = await this.post(ROUTES.MARKET_FEED_V1, payload);
25657
+ if (res.Data && Array.isArray(res.Data)) {
25658
+ for (let i = 0;i < res.Data.length; i++) {
25659
+ result.set(symbols[i], fromMarketFeed(res.Data[i]));
25660
+ }
25661
+ }
25662
+ return Ok(result);
25663
+ } catch (err) {
25664
+ return Err(err instanceof Error ? err : new Error(String(err)));
25665
+ }
25666
+ }
25667
+ async getOptionChain(_underlying, _expiry) {
25668
+ return Err(new Error("5paisa does not expose a direct option chain API via OpenAPI."));
25669
+ }
25670
+ async getCandles(symbol, interval, from, to) {
25671
+ try {
25672
+ const token = this.session?.accessToken ?? "";
25673
+ const parts = symbol.split(":");
25674
+ let exch = "N";
25675
+ let exchType = "C";
25676
+ let scripCode = "";
25677
+ if (parts.length === 3) {
25678
+ exch = parts[0];
25679
+ exchType = parts[1];
25680
+ scripCode = parts[2];
25681
+ } else if (parts.length === 2) {
25682
+ exch = parts[0];
25683
+ scripCode = parts[1];
25684
+ } else {
25685
+ scripCode = parts[0];
25686
+ }
25687
+ const timeframe = CANDLE_INTERVAL_TO_5P[interval] ?? "1d";
25688
+ const fromStr = from.toISOString().slice(0, 10);
25689
+ const toStr = to.toISOString().slice(0, 10);
25690
+ const url = `${FIVEPAISA_HISTORICAL_BASE}/${exch}/${exchType}/${scripCode}/${timeframe}?from=${fromStr}&end=${toStr}`;
25691
+ const res = await fetch(url, {
25692
+ headers: {
25693
+ "Content-Type": "application/json",
25694
+ Authorization: `bearer ${token}`
25695
+ }
25696
+ });
25697
+ const text = await res.text();
25698
+ let json;
25699
+ try {
25700
+ json = JSON.parse(text);
25701
+ } catch {
25702
+ return Err(new Error(`Historical API returned non-JSON (${res.status}): ${text.slice(0, 200)}`));
25703
+ }
25704
+ if (json.status !== "success" && json.message) {
25705
+ return Err(new Error(`Historical API error: ${json.message}`));
25706
+ }
25707
+ const rawCandles = json.data?.candles;
25708
+ if (!Array.isArray(rawCandles))
25709
+ return Ok([]);
25710
+ const candles = rawCandles.map((c) => ({
25711
+ timestamp: new Date(String(c[0])).getTime(),
25712
+ open: Number(c[1]) || 0,
25713
+ high: Number(c[2]) || 0,
25714
+ low: Number(c[3]) || 0,
25715
+ close: Number(c[4]) || 0,
25716
+ volume: Number(c[5]) || 0
25717
+ }));
25718
+ return Ok(candles);
25719
+ } catch (err) {
25720
+ return Err(err instanceof Error ? err : new Error(String(err)));
25721
+ }
25722
+ }
25723
+ toBrokerToken(instrument, _format = "ws") {
25724
+ const fp = instrument.brokerTokens.fivepaisa;
25725
+ if (!fp?.scripCode)
25726
+ return null;
25727
+ const exch = EXCHANGE_TO_5P[instrument.exchange] ?? "N";
25728
+ const exchType = fp.exchangeType ?? EXCHANGE_TYPE_MAP[instrument.exchange] ?? "C";
25729
+ return `${exch}:${exchType}:${fp.scripCode}`;
25730
+ }
25731
+ getIndexTokens(format = "ws") {
25732
+ const result = new Map;
25733
+ for (const inst of this._iMaster.getIndices()) {
25734
+ const token = this.toBrokerToken(inst, format);
25735
+ if (token)
25736
+ result.set(inst.underlying.toUpperCase(), token);
25737
+ }
25738
+ return result;
25739
+ }
25740
+ subscribeTicks(tokens, cb) {
25741
+ this.ensureSocket();
25742
+ return this.socket.subscribe(tokens, cb);
25743
+ }
25744
+ unsubscribeTicks(sub) {
25745
+ if (this.socket) {
25746
+ this.socket.unsubscribe(sub);
25747
+ }
25748
+ }
25749
+ getConnectionState() {
25750
+ return this.socket?.getConnectionState() ?? "DISCONNECTED";
25751
+ }
25752
+ async getMargins() {
25753
+ try {
25754
+ const payload = {
25755
+ head: { ...this.buildHead(), requestCode: REQUEST_CODES.MARGIN },
25756
+ body: this.buildBody()
25757
+ };
25758
+ const res = await this.post(ROUTES.MARGIN, payload);
25759
+ if (!res.EquityMargin || res.EquityMargin.length === 0) {
25760
+ return Err(new Error(`Failed to fetch margins: ${res.Message ?? "no EquityMargin in response"}`));
25761
+ }
25762
+ const m = res.EquityMargin[0];
25763
+ const available = Number(m.AvailableMargin ?? m.NetAvailableMargin ?? 0);
25764
+ const used = Number(m.MarginUsed ?? 0);
25765
+ const collateral = Number(m.Collateral ?? 0);
25766
+ const total = available + used;
25767
+ return Ok({
25768
+ available,
25769
+ used,
25770
+ total,
25771
+ collateral,
25772
+ segments: {
25773
+ equity: { available, used }
25774
+ }
25775
+ });
25776
+ } catch (err) {
25777
+ return Err(err instanceof Error ? err : new Error(String(err)));
25778
+ }
25779
+ }
25780
+ async getFunds() {
25781
+ try {
25782
+ const margins = await this.getMargins();
25783
+ if (!margins.ok)
25784
+ return margins;
25785
+ return Ok({
25786
+ equity: margins.value.available,
25787
+ commodity: 0,
25788
+ total: margins.value.available
25789
+ });
25790
+ } catch (err) {
25791
+ return Err(err instanceof Error ? err : new Error(String(err)));
25792
+ }
25793
+ }
25794
+ async getTradeHistory(_from, _to) {
25795
+ try {
25796
+ const payload = {
25797
+ head: { ...this.buildHead(), requestCode: REQUEST_CODES.TRADE_BOOK },
25798
+ body: this.buildBody()
25799
+ };
25800
+ const res = await this.post(ROUTES.TRADE_BOOK, payload);
25801
+ if (!res.TradeBookDetail || res.TradeBookDetail.length === 0) {
25802
+ return Ok([]);
25803
+ }
25804
+ return Ok(res.TradeBookDetail.map(fromTradeBookEntry));
25805
+ } catch (err) {
25806
+ return Err(err instanceof Error ? err : new Error(String(err)));
25807
+ }
25808
+ }
25809
+ async getPnLReport(_from, _to) {
25810
+ return Err(new Error("5paisa does not expose a direct PnL report API."));
25811
+ }
25812
+ parseSymbolsToFeedData(symbols) {
25813
+ return symbols.map((sym) => {
25814
+ const parts = sym.split(":");
25815
+ if (parts.length === 3) {
25816
+ return { Exch: parts[0], ExchType: parts[1], ScripCode: parseInt(parts[2], 10) };
25817
+ }
25818
+ if (parts.length === 2) {
25819
+ return { Exch: parts[0], ExchType: "C", ScripCode: parseInt(parts[1], 10) };
25820
+ }
25821
+ return { Exch: "N", ExchType: "C", ScripCode: parseInt(parts[0], 10) };
25822
+ });
25823
+ }
25824
+ buildHead() {
25825
+ return {
25826
+ appName: this.credentials?.appName ?? "",
25827
+ appVer: "1.0",
25828
+ key: this.credentials?.userKey ?? this.credentials?.apiKey ?? "",
25829
+ osName: "WEB",
25830
+ requestCode: "",
25831
+ userId: this.credentials?.userId ?? "",
25832
+ password: this.credentials?.password ?? ""
25833
+ };
25834
+ }
25835
+ buildBody() {
25836
+ return {
25837
+ ClientCode: this.session?.userId ?? ""
25838
+ };
25839
+ }
25840
+ async post(url, payload) {
25841
+ if (!this.session)
25842
+ throw new Error("Not authenticated");
25843
+ console.log(`[5paisa] POST ${url}`);
25844
+ console.log(`[5paisa] Payload:`, JSON.stringify(payload).slice(0, 500));
25845
+ const response = await fetch(url, {
25846
+ method: "POST",
25847
+ headers: {
25848
+ "Content-Type": "application/json",
25849
+ Authorization: `Bearer ${this.session.accessToken}`
25850
+ },
25851
+ body: JSON.stringify(payload)
25852
+ });
25853
+ const text = await response.text();
25854
+ let json;
25855
+ try {
25856
+ json = JSON.parse(text);
25857
+ } catch {
25858
+ throw new Error(`5paisa API error (${response.status}): non-JSON response: ${text.slice(0, 200)}`);
25859
+ }
25860
+ if (!json.body) {
25861
+ throw new Error(`5paisa API error (${response.status}): no body in response`);
25862
+ }
25863
+ return json.body;
25864
+ }
25865
+ ensureSocket() {
25866
+ if (!this.session)
25867
+ throw new Error("Cannot create socket without an active session");
25868
+ if (this.socket) {
25869
+ if (this.socket.getConnectionState() === "DISCONNECTED") {
25870
+ this.socket.reconnect(this.session.accessToken);
25871
+ }
25872
+ return;
25873
+ }
25874
+ this.socket = new FivePaisaSocket(this.session.userId, this.session.accessToken);
25875
+ this.socket.connect();
25876
+ }
25877
+ }
24012
25878
  // src/index.ts
24013
- var VERSION = "0.3.0";
24014
25879
  function createBroker(brokerId, options) {
24015
25880
  switch (brokerId) {
24016
25881
  case "zerodha":
@@ -24019,6 +25884,8 @@ function createBroker(brokerId, options) {
24019
25884
  return new FinvasiaBroker(options);
24020
25885
  case "dhan":
24021
25886
  return new DhanBroker(options);
25887
+ case "fivepaisa":
25888
+ return new FivePaisaBroker(options);
24022
25889
  case "paper":
24023
25890
  return new PaperBroker(options);
24024
25891
  default: {
@@ -24034,7 +25901,6 @@ export {
24034
25901
  createBroker,
24035
25902
  ZerodhaBroker,
24036
25903
  WSManager,
24037
- VERSION,
24038
25904
  SessionManager,
24039
25905
  SessionExpiredError,
24040
25906
  RateLimitError,
@@ -24044,6 +25910,7 @@ export {
24044
25910
  NsekitError,
24045
25911
  InstrumentNotFoundError,
24046
25912
  InstrumentMaster,
25913
+ FivePaisaBroker,
24047
25914
  FinvasiaBroker,
24048
25915
  Err,
24049
25916
  DhanBroker,