nsekit 0.3.2 → 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 (47) hide show
  1. package/README.md +3 -2
  2. package/dist/brokers/dhan/DhanBroker.d.ts +3 -0
  3. package/dist/brokers/dhan/DhanBroker.d.ts.map +1 -1
  4. package/dist/brokers/dhan/dhan-auth.d.ts +8 -3
  5. package/dist/brokers/dhan/dhan-auth.d.ts.map +1 -1
  6. package/dist/brokers/dhan/dhan-instruments.d.ts.map +1 -1
  7. package/dist/brokers/dhan/dhan-socket.d.ts.map +1 -1
  8. package/dist/brokers/finvasia/FinvasiaBroker.d.ts +36 -1
  9. package/dist/brokers/finvasia/FinvasiaBroker.d.ts.map +1 -1
  10. package/dist/brokers/finvasia/finvasia-auth.d.ts +40 -4
  11. package/dist/brokers/finvasia/finvasia-auth.d.ts.map +1 -1
  12. package/dist/brokers/finvasia/finvasia-constants.d.ts +3 -1
  13. package/dist/brokers/finvasia/finvasia-constants.d.ts.map +1 -1
  14. package/dist/brokers/finvasia/finvasia-instruments.d.ts.map +1 -1
  15. package/dist/brokers/finvasia/finvasia-socket.d.ts +2 -1
  16. package/dist/brokers/finvasia/finvasia-socket.d.ts.map +1 -1
  17. package/dist/brokers/fivepaisa/FivePaisaBroker.d.ts +70 -0
  18. package/dist/brokers/fivepaisa/FivePaisaBroker.d.ts.map +1 -0
  19. package/dist/brokers/fivepaisa/fivepaisa-auth.d.ts +53 -0
  20. package/dist/brokers/fivepaisa/fivepaisa-auth.d.ts.map +1 -0
  21. package/dist/brokers/fivepaisa/fivepaisa-constants.d.ts +57 -0
  22. package/dist/brokers/fivepaisa/fivepaisa-constants.d.ts.map +1 -0
  23. package/dist/brokers/fivepaisa/fivepaisa-instruments.d.ts +34 -0
  24. package/dist/brokers/fivepaisa/fivepaisa-instruments.d.ts.map +1 -0
  25. package/dist/brokers/fivepaisa/fivepaisa-mapper.d.ts +170 -0
  26. package/dist/brokers/fivepaisa/fivepaisa-mapper.d.ts.map +1 -0
  27. package/dist/brokers/fivepaisa/fivepaisa-socket.d.ts +67 -0
  28. package/dist/brokers/fivepaisa/fivepaisa-socket.d.ts.map +1 -0
  29. package/dist/brokers/paper/PaperBroker.d.ts +3 -0
  30. package/dist/brokers/paper/PaperBroker.d.ts.map +1 -1
  31. package/dist/brokers/zerodha/ZerodhaBroker.d.ts +3 -0
  32. package/dist/brokers/zerodha/ZerodhaBroker.d.ts.map +1 -1
  33. package/dist/brokers/zerodha/zerodha-instruments.d.ts.map +1 -1
  34. package/dist/index.d.ts +2 -2
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +1704 -78
  37. package/dist/instruments/instrument-master.d.ts +4 -0
  38. package/dist/instruments/instrument-master.d.ts.map +1 -1
  39. package/dist/interfaces/broker.interface.d.ts +7 -0
  40. package/dist/interfaces/broker.interface.d.ts.map +1 -1
  41. package/dist/types/broker.d.ts +7 -0
  42. package/dist/types/broker.d.ts.map +1 -1
  43. package/dist/types/common.d.ts +2 -2
  44. package/dist/types/common.d.ts.map +1 -1
  45. package/dist/types/instruments.d.ts +4 -0
  46. package/dist/types/instruments.d.ts.map +1 -1
  47. package/package.json +52 -61
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);
@@ -19596,6 +19604,11 @@ class InstrumentMaster {
19596
19604
  securityId: entry.brokerToken,
19597
19605
  sid: entry.brokerSymbol
19598
19606
  };
19607
+ } else if (brokerId === "fivepaisa") {
19608
+ instrument.brokerTokens.fivepaisa = {
19609
+ scripCode: entry.brokerToken,
19610
+ exchangeType: entry.raw?.["ExchType"] ?? "C"
19611
+ };
19599
19612
  }
19600
19613
  if (entry.expiry && !instrument.expiry)
19601
19614
  instrument.expiry = entry.expiry;
@@ -19627,7 +19640,9 @@ class InstrumentMaster {
19627
19640
  delete instrument.brokerTokens.finvasia;
19628
19641
  else if (brokerId === "dhan")
19629
19642
  delete instrument.brokerTokens.dhan;
19630
- 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;
19631
19646
  if (!hasAnyBroker) {
19632
19647
  this.instruments.delete(key);
19633
19648
  }
@@ -20593,8 +20608,9 @@ class ZerodhaInstruments {
20593
20608
  const lotSizeRaw = fields["lot_size"] ?? fields[COL.LOT_SIZE] ?? "1";
20594
20609
  const tickSizeRaw = fields["tick_size"] ?? fields[COL.TICK_SIZE] ?? "0.05";
20595
20610
  const instTypeRaw = fields["instrument_type"] ?? fields[COL.INSTRUMENT_TYPE] ?? "EQ";
20611
+ const segmentRaw = fields["segment"] ?? fields[COL.SEGMENT] ?? "";
20596
20612
  const exchange = EXCHANGE_MAP[exchangeRaw] ?? "NSE";
20597
- const instrumentType = mapInstrumentType(instTypeRaw);
20613
+ const instrumentType = segmentRaw.toUpperCase() === "INDICES" ? "IDX" : mapInstrumentType(instTypeRaw);
20598
20614
  return {
20599
20615
  tradingSymbol,
20600
20616
  exchange,
@@ -20655,6 +20671,9 @@ class ZerodhaBroker {
20655
20671
  return null;
20656
20672
  return new Date(this.session.expiresAt);
20657
20673
  }
20674
+ getSession() {
20675
+ return this.session;
20676
+ }
20658
20677
  async sync(force) {
20659
20678
  return this._iMaster.syncBroker(this.id, this.instruments, force);
20660
20679
  }
@@ -20840,6 +20859,25 @@ class ZerodhaBroker {
20840
20859
  return Err(err instanceof Error ? err : new Error(String(err)));
20841
20860
  }
20842
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
+ }
20843
20881
  subscribeTicks(tokens, cb) {
20844
20882
  this.ensureSocket();
20845
20883
  return this.socket.subscribe(tokens, cb);
@@ -21012,8 +21050,9 @@ var VALIDITY_FROM_SHOONYA = {
21012
21050
  DAY: "DAY",
21013
21051
  IOC: "IOC"
21014
21052
  };
21015
- var SHOONYA_API_BASE = "https://api.shoonya.com/NorenWClientTP";
21053
+ var SHOONYA_API_BASE = "https://api.shoonya.com/NorenWClientAPI";
21016
21054
  var SHOONYA_WS_URL = "wss://api.shoonya.com/NorenWSTP/";
21055
+ var SHOONYA_WS_OAUTH_URL = "wss://api.shoonya.com/NorenWSAPI/";
21017
21056
  var SHOONYA_INSTRUMENTS_BASE = "https://api.shoonya.com/";
21018
21057
  var SHOONYA_INSTRUMENT_FILES = {
21019
21058
  NSE: "NSE_symbols.txt.zip",
@@ -21033,6 +21072,7 @@ var CANDLE_INTERVAL_TO_SHOONYA = {
21033
21072
 
21034
21073
  // src/brokers/finvasia/finvasia-auth.ts
21035
21074
  import crypto2 from "crypto";
21075
+ var SHOONYA_OAUTH_BASE = "https://trade.shoonya.com/OAuthlogin/authorize/oauth";
21036
21076
  function generateTOTP(secret) {
21037
21077
  const base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
21038
21078
  const decoded = [];
@@ -21057,7 +21097,7 @@ function generateTOTP(secret) {
21057
21097
  return String(code % 1e6).padStart(6, "0");
21058
21098
  }
21059
21099
  async function authenticate2(creds) {
21060
- const { userId, password, totpSecret, apiKey } = creds;
21100
+ const { userId, password, totpSecret, apiKey, vendorCode } = creds;
21061
21101
  if (!userId)
21062
21102
  return Err(new Error("Missing userId in credentials"));
21063
21103
  if (!password)
@@ -21069,28 +21109,42 @@ async function authenticate2(creds) {
21069
21109
  try {
21070
21110
  const totp = generateTOTP(totpSecret);
21071
21111
  const passwordHash = crypto2.createHash("sha256").update(password).digest("hex");
21072
- 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");
21073
21114
  const jData = JSON.stringify({
21074
21115
  source: "API",
21075
- apkversion: "1.0.0",
21116
+ apkversion: "1",
21076
21117
  uid: userId,
21077
21118
  pwd: passwordHash,
21078
21119
  factor2: totp,
21079
- vc: creds.vendorCode ?? apiKey,
21120
+ vc,
21080
21121
  appkey: appKey,
21081
21122
  imei: "nsekit"
21082
21123
  });
21083
- const response = await fetch(`${SHOONYA_API_BASE}/QuickAuth`, {
21084
- method: "POST",
21085
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
21086
- body: `jData=${jData}`
21087
- });
21088
- const text = await response.text();
21089
- let json;
21090
- try {
21091
- json = JSON.parse(text);
21092
- } catch {
21093
- 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)}`));
21094
21148
  }
21095
21149
  if (json.stat !== "Ok" || !json.susertoken) {
21096
21150
  return Err(new Error(`Shoonya login failed: ${json.emsg ?? "Unknown error"}`));
@@ -21112,6 +21166,87 @@ async function authenticate2(creds) {
21112
21166
  async function refreshSession2(_session) {
21113
21167
  return Err(new Error("Finvasia/Shoonya does not support session refresh. Re-authenticate with TOTP."));
21114
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
+ }
21115
21250
  function isSessionValid2(session) {
21116
21251
  if (!session)
21117
21252
  return false;
@@ -21339,28 +21474,37 @@ class FinvasiaSocket {
21339
21474
  subscribedTokens = new Set;
21340
21475
  userId;
21341
21476
  accessToken;
21477
+ authMethod;
21342
21478
  reconnectAttempts = 0;
21343
21479
  maxReconnects = 5;
21344
21480
  reconnectTimer = null;
21345
21481
  onAuthFailed;
21346
- constructor(userId, accessToken, onAuthFailed) {
21482
+ constructor(userId, accessToken, onAuthFailed, authMethod = "totp") {
21347
21483
  this.userId = userId;
21348
21484
  this.accessToken = accessToken;
21485
+ this.authMethod = authMethod;
21349
21486
  this.onAuthFailed = onAuthFailed;
21350
21487
  }
21351
21488
  connect() {
21352
21489
  if (this.state === "CONNECTED" || this.state === "CONNECTING")
21353
21490
  return;
21354
21491
  this.state = "CONNECTING";
21492
+ const wsUrl = this.authMethod === "oauth" ? SHOONYA_WS_OAUTH_URL : SHOONYA_WS_URL;
21355
21493
  try {
21356
- this.ws = new WS(SHOONYA_WS_URL);
21494
+ this.ws = new WS(wsUrl);
21357
21495
  } catch {
21358
21496
  this.state = "DISCONNECTED";
21359
21497
  return;
21360
21498
  }
21361
21499
  this.ws.on("open", () => {
21362
- console.log(`[FinvasiaSocket] WS open, sending auth for uid=${this.userId}`);
21363
- 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({
21364
21508
  t: "c",
21365
21509
  uid: this.userId,
21366
21510
  actid: this.userId,
@@ -21463,7 +21607,13 @@ class FinvasiaSocket {
21463
21607
  }
21464
21608
  handleMessage(data) {
21465
21609
  const type = data["t"];
21466
- 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") {
21467
21617
  if (data["s"] === "OK") {
21468
21618
  this.state = "CONNECTED";
21469
21619
  this.reconnectAttempts = 0;
@@ -21471,7 +21621,7 @@ class FinvasiaSocket {
21471
21621
  this.sendSubscribe(Array.from(this.subscribedTokens));
21472
21622
  }
21473
21623
  } else {
21474
- console.error(`[FinvasiaSocket] Auth rejected (ck NOT_OK) \u2014 token invalid, stopping reconnect`);
21624
+ console.error(`[FinvasiaSocket] Auth rejected (${type} NOT_OK) \u2014 token invalid, stopping reconnect`);
21475
21625
  this.reconnectAttempts = this.maxReconnects;
21476
21626
  this.state = "DISCONNECTED";
21477
21627
  this.onAuthFailed?.();
@@ -21600,7 +21750,9 @@ function mapInstrumentType2(instType, optionType) {
21600
21750
  if (upper === "PE")
21601
21751
  return "PE";
21602
21752
  const it = instType?.toUpperCase();
21603
- if (it === "EQ" || it === "EQUITY" || it === "INDEX")
21753
+ if (it === "INDEX")
21754
+ return "IDX";
21755
+ if (it === "EQ" || it === "EQUITY")
21604
21756
  return "EQ";
21605
21757
  if (it?.startsWith("FUT") || it?.includes("FUT"))
21606
21758
  return "FUT";
@@ -21618,10 +21770,23 @@ function parseStrike2(raw) {
21618
21770
  const n = parseFloat(raw);
21619
21771
  return isNaN(n) || n === 0 ? undefined : n;
21620
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
+ };
21621
21785
  function deriveUnderlying2(symbol, tradingSymbol, instType) {
21622
21786
  if (instType === "EQ")
21623
21787
  return symbol || tradingSymbol;
21624
- return symbol || tradingSymbol.replace(/\d.*/g, "");
21788
+ const raw = symbol || tradingSymbol.replace(/\d.*/g, "");
21789
+ return UNDERLYING_ALIASES[raw] ?? raw;
21625
21790
  }
21626
21791
 
21627
21792
  class FinvasiaInstruments {
@@ -21780,6 +21945,25 @@ class FinvasiaBroker {
21780
21945
  this._iMaster = new InstrumentMaster(options);
21781
21946
  }
21782
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
+ }
21783
21967
  const result = await authenticate2(creds);
21784
21968
  if (!result.ok)
21785
21969
  return result;
@@ -21788,6 +21972,9 @@ class FinvasiaBroker {
21788
21972
  this.instrumentsImpl.setCredentials(this.session.userId, this.session.accessToken);
21789
21973
  return result;
21790
21974
  }
21975
+ getOAuthURL(clientId) {
21976
+ return getOAuthURL(clientId);
21977
+ }
21791
21978
  async refreshSession(session) {
21792
21979
  return refreshSession2(session);
21793
21980
  }
@@ -21799,6 +21986,19 @@ class FinvasiaBroker {
21799
21986
  return null;
21800
21987
  return new Date(this.session.expiresAt);
21801
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
+ }
21802
22002
  async sync(force) {
21803
22003
  return this._iMaster.syncBroker(this.id, this.instruments, force);
21804
22004
  }
@@ -21904,6 +22104,10 @@ class FinvasiaBroker {
21904
22104
  const uid = this.session?.userId ?? "";
21905
22105
  const raw = await this.post("OrderBook", { uid });
21906
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
+ }
21907
22111
  return Ok([]);
21908
22112
  }
21909
22113
  return Ok(raw.map(fromOrder2));
@@ -21936,8 +22140,13 @@ class FinvasiaBroker {
21936
22140
  try {
21937
22141
  const uid = this.session?.userId ?? "";
21938
22142
  const raw = await this.post("PositionBook", { uid, actid: uid });
21939
- 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
+ }
21940
22148
  return Ok([]);
22149
+ }
21941
22150
  return Ok(raw.map(fromPosition2));
21942
22151
  } catch (err) {
21943
22152
  return Err(err instanceof Error ? err : new Error(String(err)));
@@ -21951,8 +22160,13 @@ class FinvasiaBroker {
21951
22160
  actid: uid,
21952
22161
  prd: "C"
21953
22162
  });
21954
- 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
+ }
21955
22168
  return Ok([]);
22169
+ }
21956
22170
  return Ok(raw.map(fromHolding2));
21957
22171
  } catch (err) {
21958
22172
  return Err(err instanceof Error ? err : new Error(String(err)));
@@ -22076,6 +22290,22 @@ class FinvasiaBroker {
22076
22290
  return Err(err instanceof Error ? err : new Error(String(err)));
22077
22291
  }
22078
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
+ }
22079
22309
  subscribeTicks(tokens, cb) {
22080
22310
  this.ensureSocket();
22081
22311
  return this.socket.subscribe(tokens, cb);
@@ -22100,18 +22330,25 @@ class FinvasiaBroker {
22100
22330
  actid: uid
22101
22331
  });
22102
22332
  if (raw.stat !== "Ok") {
22103
- return Err(new Error("Failed to fetch margins"));
22333
+ return Err(new Error(`Margins failed: ${raw.emsg ?? "Unknown error"}`));
22104
22334
  }
22105
22335
  const cash = parseFloat(raw["cash"] ?? "0");
22106
- const marginUsed = parseFloat(raw["marginused"] ?? "0");
22336
+ const marginUsed = parseFloat(raw["marginused"] ?? raw["peak_mar"] ?? "0");
22337
+ const blockedAmt = parseFloat(raw["blk_amt"] ?? "0");
22107
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;
22108
22344
  return Ok({
22109
- available: cash - marginUsed,
22110
- used: marginUsed,
22111
- total: cash,
22345
+ available,
22346
+ used,
22347
+ total,
22112
22348
  collateral,
22113
22349
  segments: {
22114
- equity: { available: cash - marginUsed, used: marginUsed }
22350
+ equity: { available: eqAvailable, used: 0 },
22351
+ commodity: { available: comAvailable, used: 0 }
22115
22352
  }
22116
22353
  });
22117
22354
  } catch (err) {
@@ -22123,9 +22360,11 @@ class FinvasiaBroker {
22123
22360
  const margins = await this.getMargins();
22124
22361
  if (!margins.ok)
22125
22362
  return margins;
22363
+ const eq = margins.value.segments["equity"]?.available ?? 0;
22364
+ const com = margins.value.segments["commodity"]?.available ?? 0;
22126
22365
  return Ok({
22127
- equity: margins.value.available,
22128
- commodity: 0,
22366
+ equity: eq,
22367
+ commodity: com,
22129
22368
  total: margins.value.available
22130
22369
  });
22131
22370
  } catch (err) {
@@ -22172,10 +22411,18 @@ class FinvasiaBroker {
22172
22411
  if (!this.session)
22173
22412
  throw new Error("Not authenticated");
22174
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}`;
22175
22422
  const response = await fetch(`${SHOONYA_API_BASE}/${endpoint}`, {
22176
22423
  method: "POST",
22177
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
22178
- body: `jData=${jData}&jKey=${this.session.accessToken}`
22424
+ headers,
22425
+ body
22179
22426
  });
22180
22427
  const text = await response.text();
22181
22428
  let parsed;
@@ -22184,36 +22431,32 @@ class FinvasiaBroker {
22184
22431
  } catch {
22185
22432
  throw new Error(`Shoonya ${endpoint} failed (${response.status}): ${text.slice(0, 200)}`);
22186
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
+ }
22187
22441
  return parsed;
22188
22442
  }
22189
22443
  ensureSocket() {
22190
- if (this.socket)
22191
- return;
22192
22444
  if (!this.session)
22193
22445
  throw new Error("Cannot create socket without an active session");
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";
22194
22453
  this.socket = new FinvasiaSocket(this.session.userId, this.session.accessToken, () => {
22195
22454
  this.handleSocketAuthFailed();
22196
- });
22455
+ }, authMethod);
22197
22456
  this.socket.connect();
22198
22457
  }
22199
22458
  handleSocketAuthFailed() {
22200
- if (!this.credentials) {
22201
- console.error("[FinvasiaBroker] Socket auth failed but no credentials stored \u2014 cannot re-authenticate");
22202
- return;
22203
- }
22204
- console.log("[FinvasiaBroker] Socket auth failed, re-authenticating...");
22205
- authenticate2(this.credentials).then((result) => {
22206
- if (!result.ok) {
22207
- console.error("[FinvasiaBroker] Re-authentication failed:", result.error instanceof Error ? result.error.message : result.error);
22208
- return;
22209
- }
22210
- this.session = result.value;
22211
- this.instrumentsImpl.setCredentials(this.session.userId, this.session.accessToken);
22212
- console.log("[FinvasiaBroker] Re-authenticated successfully, reconnecting socket");
22213
- this.socket?.reconnect(this.session.accessToken);
22214
- }).catch((err) => {
22215
- console.error("[FinvasiaBroker] Re-authentication exception:", err instanceof Error ? err.message : err);
22216
- });
22459
+ console.warn("[FinvasiaBroker] Socket auth rejected (ck NOT_OK) \u2014 waiting for user-bot to re-authenticate");
22217
22460
  }
22218
22461
  }
22219
22462
  // src/brokers/dhan/dhan-constants.ts
@@ -22315,12 +22558,12 @@ async function authenticate3(creds) {
22315
22558
  return Err(new Error(`Dhan auth validation failed (${response.status}): ${body}`));
22316
22559
  }
22317
22560
  const profile = await response.json();
22318
- const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000;
22561
+ const twentyFourHoursMs = 24 * 60 * 60 * 1000;
22319
22562
  const session = {
22320
22563
  accessToken,
22321
22564
  refreshToken: undefined,
22322
- expiresAt: Date.now() + thirtyDaysMs,
22323
- ttlSeconds: 30 * 24 * 60 * 60,
22565
+ expiresAt: Date.now() + twentyFourHoursMs,
22566
+ ttlSeconds: 24 * 60 * 60,
22324
22567
  brokerId: "dhan",
22325
22568
  userId: profile.clientId ?? clientId,
22326
22569
  metadata: { name: profile.name, email: profile.email }
@@ -22385,15 +22628,16 @@ async function consumeConsent(creds, tokenId) {
22385
22628
  return Err(new Error(`Dhan consumeApp-consent failed (${response.status}): ${body}`));
22386
22629
  }
22387
22630
  const data = await response.json();
22388
- if (!data.accessToken) {
22389
- return Err(new Error(data.message ?? "Failed to get access token \u2014 no accessToken returned"));
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"));
22390
22634
  }
22391
- const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000;
22635
+ const twentyFourHoursMs = 24 * 60 * 60 * 1000;
22392
22636
  const session = {
22393
- accessToken: data.accessToken,
22637
+ accessToken: newToken,
22394
22638
  refreshToken: undefined,
22395
- expiresAt: Date.now() + thirtyDaysMs,
22396
- ttlSeconds: 30 * 24 * 60 * 60,
22639
+ expiresAt: Date.now() + twentyFourHoursMs,
22640
+ ttlSeconds: 24 * 60 * 60,
22397
22641
  brokerId: "dhan",
22398
22642
  userId: clientId ?? "",
22399
22643
  metadata: { authMode: "oauth" }
@@ -22403,8 +22647,42 @@ async function consumeConsent(creds, tokenId) {
22403
22647
  return Err(err instanceof Error ? err : new Error(String(err)));
22404
22648
  }
22405
22649
  }
22406
- async function refreshSession3(_session) {
22407
- return Err(new Error("Dhan does not support token refresh. Generate a new access token from the Dhan developer portal."));
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
+ }
22408
22686
  }
22409
22687
  function isSessionValid3(session) {
22410
22688
  if (!session)
@@ -22639,6 +22917,7 @@ class DhanSocket {
22639
22917
  return;
22640
22918
  }
22641
22919
  this.ws.on("open", () => {
22920
+ console.log(`[DhanSocket] WS connected for clientId=${this.clientId}`);
22642
22921
  this.state = "CONNECTED";
22643
22922
  this.reconnectAttempts = 0;
22644
22923
  this.startHeartbeat();
@@ -22648,6 +22927,11 @@ class DhanSocket {
22648
22927
  });
22649
22928
  this.ws.on("message", (raw, isBinary) => {
22650
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
+ }
22651
22935
  if (isBinary) {
22652
22936
  const buf = raw instanceof ArrayBuffer ? Buffer.from(raw) : Buffer.isBuffer(raw) ? raw : Array.isArray(raw) ? Buffer.concat(raw) : Buffer.from(raw);
22653
22937
  this.handleBinaryMessage(buf);
@@ -22657,12 +22941,20 @@ class DhanSocket {
22657
22941
  }
22658
22942
  } catch {}
22659
22943
  });
22660
- this.ws.on("close", () => {
22944
+ this.ws.on("close", (code, reason) => {
22945
+ console.warn(`[DhanSocket] WS closed: code=${code}, reason=${reason.toString()}`);
22661
22946
  this.state = "DISCONNECTED";
22662
22947
  this.stopHeartbeat();
22663
22948
  this.attemptReconnect();
22664
22949
  });
22665
- 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
+ });
22666
22958
  }
22667
22959
  disconnect() {
22668
22960
  this.clearReconnectTimer();
@@ -22727,7 +23019,7 @@ class DhanSocket {
22727
23019
  const exchSegByte = data.readUInt8(3);
22728
23020
  const securityId = data.readInt32LE(4);
22729
23021
  const exchangeSegment = EXCH_SEG_FROM_BYTE[exchSegByte] ?? "NSE_EQ";
22730
- const fullKey = `${exchangeSegment}:${securityId}`;
23022
+ const fullKey = `${exchangeSegment}|${securityId}`;
22731
23023
  if (responseCode === 1 && data.length >= 32) {
22732
23024
  const ltp = data.readFloatLE(8);
22733
23025
  const ltt = data.readInt32LE(12);
@@ -22832,7 +23124,9 @@ class DhanSocket {
22832
23124
  InstrumentCount: instruments.length,
22833
23125
  InstrumentList: instruments
22834
23126
  };
22835
- this.ws.send(JSON.stringify(request));
23127
+ const msg = JSON.stringify(request);
23128
+ console.log(`[DhanSocket] Sending subscribe: ${msg}`);
23129
+ this.ws.send(msg);
22836
23130
  }
22837
23131
  sendUnsubscribe(tokens) {
22838
23132
  if (!this.ws || this.state !== "CONNECTED")
@@ -22923,7 +23217,9 @@ function mapInstrumentType3(instName, optionType) {
22923
23217
  if (opt === "PE")
22924
23218
  return "PE";
22925
23219
  const inst = instName?.toUpperCase();
22926
- if (inst?.includes("EQUITY") || inst === "INDEX" || inst === "EQ")
23220
+ if (inst === "INDEX")
23221
+ return "IDX";
23222
+ if (inst?.includes("EQUITY") || inst === "EQ")
22927
23223
  return "EQ";
22928
23224
  if (inst?.includes("FUT"))
22929
23225
  return "FUT";
@@ -23141,7 +23437,14 @@ class DhanBroker {
23141
23437
  return result;
23142
23438
  }
23143
23439
  async refreshSession(session) {
23144
- 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;
23145
23448
  }
23146
23449
  isSessionValid() {
23147
23450
  return isSessionValid3(this.session);
@@ -23151,6 +23454,9 @@ class DhanBroker {
23151
23454
  return null;
23152
23455
  return new Date(this.session.expiresAt);
23153
23456
  }
23457
+ getSession() {
23458
+ return this.session;
23459
+ }
23154
23460
  async generateConsent(creds) {
23155
23461
  this.oauthCreds = creds;
23156
23462
  this.clientId = creds.userId ?? "";
@@ -23429,6 +23735,23 @@ class DhanBroker {
23429
23735
  return Err(err instanceof Error ? err : new Error(String(err)));
23430
23736
  }
23431
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
+ }
23432
23755
  subscribeTicks(tokens, cb) {
23433
23756
  this.ensureSocket();
23434
23757
  return this.socket.subscribe(tokens, cb);
@@ -23448,7 +23771,8 @@ class DhanBroker {
23448
23771
  async getMargins() {
23449
23772
  try {
23450
23773
  const raw = await this.request("GET", "/fundlimit");
23451
- 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);
23452
23776
  const used = Number(raw["utilizedAmount"] ?? raw["blockedMargin"] ?? 0);
23453
23777
  const collateral = Number(raw["collateralAmount"] ?? 0);
23454
23778
  return Ok({
@@ -23467,6 +23791,7 @@ class DhanBroker {
23467
23791
  async getFunds() {
23468
23792
  try {
23469
23793
  const margins = await this.getMargins();
23794
+ console.log("Dhan getFunds - margins:", margins);
23470
23795
  if (!margins.ok)
23471
23796
  return margins;
23472
23797
  return Ok({
@@ -23928,6 +24253,9 @@ class PaperBroker {
23928
24253
  return null;
23929
24254
  return new Date(this.session.expiresAt);
23930
24255
  }
24256
+ getSession() {
24257
+ return this.session;
24258
+ }
23931
24259
  async placeOrder(params) {
23932
24260
  try {
23933
24261
  if (params.side === "BUY") {
@@ -24059,6 +24387,12 @@ class PaperBroker {
24059
24387
  }
24060
24388
  return Err(new Error("Paper broker requires a data source for candles. Call setDataSource()."));
24061
24389
  }
24390
+ toBrokerToken(_instrument, _format = "ws") {
24391
+ return null;
24392
+ }
24393
+ getIndexTokens(_format = "ws") {
24394
+ return new Map;
24395
+ }
24062
24396
  subscribeTicks(tokens, cb) {
24063
24397
  this.subIdCounter += 1;
24064
24398
  const subId = `paper_sub_${this.subIdCounter}`;
@@ -24250,8 +24584,1298 @@ class PaperBroker {
24250
24584
  };
24251
24585
  }
24252
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
+ }
24253
25878
  // src/index.ts
24254
- var VERSION = "0.3.0";
24255
25879
  function createBroker(brokerId, options) {
24256
25880
  switch (brokerId) {
24257
25881
  case "zerodha":
@@ -24260,6 +25884,8 @@ function createBroker(brokerId, options) {
24260
25884
  return new FinvasiaBroker(options);
24261
25885
  case "dhan":
24262
25886
  return new DhanBroker(options);
25887
+ case "fivepaisa":
25888
+ return new FivePaisaBroker(options);
24263
25889
  case "paper":
24264
25890
  return new PaperBroker(options);
24265
25891
  default: {
@@ -24275,7 +25901,6 @@ export {
24275
25901
  createBroker,
24276
25902
  ZerodhaBroker,
24277
25903
  WSManager,
24278
- VERSION,
24279
25904
  SessionManager,
24280
25905
  SessionExpiredError,
24281
25906
  RateLimitError,
@@ -24285,6 +25910,7 @@ export {
24285
25910
  NsekitError,
24286
25911
  InstrumentNotFoundError,
24287
25912
  InstrumentMaster,
25913
+ FivePaisaBroker,
24288
25914
  FinvasiaBroker,
24289
25915
  Err,
24290
25916
  DhanBroker,