nsekit 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -37
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +152 -42
- package/dist/instruments/instrument-master.d.ts +60 -14
- package/dist/instruments/instrument-master.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,45 +34,102 @@ Before authenticating, review the credential fields each broker requires: [docs/
|
|
|
34
34
|
```typescript
|
|
35
35
|
import { createBroker } from 'nsekit';
|
|
36
36
|
|
|
37
|
-
//
|
|
37
|
+
const broker = createBroker('finvasia'); // or 'zerodha', 'dhan', 'paper'
|
|
38
|
+
const auth = await broker.authenticate({ userId: '...', password: '...', totpSecret: '...', apiKey: '...', vendorCode: '...' });
|
|
39
|
+
const order = await broker.placeOrder({ tradingSymbol: 'RELIANCE', exchange: 'NSE', side: 'BUY', quantity: 1, type: 'LIMIT', product: 'INTRADAY', price: 2400, validity: 'DAY' });
|
|
40
|
+
const cancel = await broker.cancelOrder(order.value.orderId);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
See [docs/broker-credentials.md](docs/broker-credentials.md) for which credential fields each broker requires.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Instrument Master
|
|
48
|
+
|
|
49
|
+
Before placing orders, searching symbols, or subscribing to ticks, sync the broker's instrument master. This downloads ~180K instruments once and indexes them in memory. Every lookup after that is instant — no API calls, no delays.
|
|
50
|
+
|
|
51
|
+
**It's always better to sync first, then run everything else.**
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { createBroker, InstrumentMaster } from 'nsekit';
|
|
55
|
+
|
|
38
56
|
const broker = createBroker('finvasia');
|
|
57
|
+
await broker.authenticate({ /* ... */ });
|
|
39
58
|
|
|
40
|
-
//
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
// Sync once at startup
|
|
60
|
+
const imaster = new InstrumentMaster();
|
|
61
|
+
await imaster.syncBroker('finvasia', broker.instruments);
|
|
62
|
+
// => { instrumentCount: 184302, source: 'network' }
|
|
63
|
+
|
|
64
|
+
// Now resolve, search, or trade
|
|
65
|
+
const inst = imaster.resolve('NIFTY-24000-CE-27FEB26'); // exact canonical lookup
|
|
66
|
+
const results = imaster.search('NIFTY', 'NFO', 'CE', 10); // fuzzy search
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Where instruments are stored
|
|
70
|
+
|
|
71
|
+
The first sync downloads instruments from the broker and saves them to disk as JSON. On subsequent startups, `syncBroker()` loads from this cache — no network call, near-instant.
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
./InstrumentDump/ (default location)
|
|
75
|
+
finvasia-instruments.json (one file per broker)
|
|
76
|
+
zerodha-instruments.json
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Custom path:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
const imaster = new InstrumentMaster({
|
|
83
|
+
instrumentFilePath: './data/instruments',
|
|
60
84
|
});
|
|
61
|
-
|
|
85
|
+
```
|
|
62
86
|
|
|
63
|
-
|
|
64
|
-
const positions = await broker.getPositions();
|
|
65
|
-
// positions.value => [{ tradingSymbol: 'RELIANCE-EQ', side: 'LONG', quantity: 1, pnl: 12.50, ... }]
|
|
87
|
+
To force a fresh download (skip cache):
|
|
66
88
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// cancel.value => { orderId: '26020900479551', status: 'CANCELLED' }
|
|
89
|
+
```typescript
|
|
90
|
+
await imaster.syncBroker('finvasia', broker.instruments, true);
|
|
70
91
|
```
|
|
71
92
|
|
|
72
|
-
|
|
93
|
+
### Multi-broker sync
|
|
94
|
+
|
|
95
|
+
Syncing multiple brokers merges instruments into one index. The same instrument from different brokers (e.g. NIFTY options) becomes a single entry with both broker tokens preserved.
|
|
73
96
|
|
|
74
97
|
```typescript
|
|
75
|
-
|
|
98
|
+
await imaster.syncBroker('zerodha', zerodhaBroker.instruments);
|
|
99
|
+
await imaster.syncBroker('finvasia', finvasiaBroker.instruments);
|
|
100
|
+
// NIFTY-24000-CE-27FEB26 now has both zerodha and finvasia tokens
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Optional: Redis
|
|
104
|
+
|
|
105
|
+
Pass an ioredis-compatible client to cache instruments in Redis. Useful when multiple processes (user bots, strategy bots) need instrument data without each loading 180K entries from disk.
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import Redis from 'ioredis';
|
|
109
|
+
|
|
110
|
+
const imaster = new InstrumentMaster({ redis: new Redis() });
|
|
111
|
+
await imaster.syncBroker('finvasia', broker.instruments);
|
|
112
|
+
// All instruments now in Redis — any process with the same Redis gets O(1) lookups
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Canonical symbol format
|
|
116
|
+
|
|
117
|
+
All instruments are indexed using a standardized format, regardless of which broker provided them:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
EQ: TATAMOTORS (just underlying)
|
|
121
|
+
FUT: NIFTY-FUT-27FEB26 (underlying-FUT-DDMMMYY)
|
|
122
|
+
CE: NIFTY-24000-CE-27FEB26 (underlying-strike-CE-DDMMMYY)
|
|
123
|
+
PE: NIFTY-24000-PE-27FEB26 (underlying-strike-PE-DDMMMYY)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Zerodha's `NIFTY26FEB24000CE` and Finvasia's `NIFTY26FEB26C24000` both resolve to `NFO:NIFTY-24000-CE-27FEB26`. You never need to think about broker-specific symbol formats.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
imaster.resolve('TATAMOTORS'); // NSE equity (NSE preferred over BSE)
|
|
130
|
+
imaster.resolve('BSE:TATAMOTORS'); // explicit exchange
|
|
131
|
+
imaster.resolve('NIFTY-24000-CE-27FEB26'); // NFO option
|
|
132
|
+
imaster.resolve('NFO:NIFTY-FUT-27FEB26'); // with exchange prefix
|
|
76
133
|
```
|
|
77
134
|
|
|
78
135
|
---
|
|
@@ -125,13 +182,7 @@ const order = unwrap(result);
|
|
|
125
182
|
|
|
126
183
|
### Instrument Master
|
|
127
184
|
|
|
128
|
-
The `
|
|
129
|
-
|
|
130
|
-
1. Redis hot cache (O(1) lookup for recently used instruments)
|
|
131
|
-
2. In-memory index (built from broker dump files)
|
|
132
|
-
3. Broker search API fallback (for brokers that support it)
|
|
133
|
-
|
|
134
|
-
Each broker implements `IBrokerInstruments` with `streamDump()`, `normalize()`, `search()`, and `resolve()` methods.
|
|
185
|
+
See the [Instrument Master](#instrument-master) section above for full usage. In short: each broker implements `IBrokerInstruments` with `streamDump()` and `normalize()`. The `normalize()` function sets `underlying`, `instrumentType`, `strike`, and `expiry` — `InstrumentMaster` builds canonical symbols automatically and merges entries across brokers.
|
|
135
186
|
|
|
136
187
|
### Paper Broker
|
|
137
188
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.2.0";
|
|
2
2
|
export * from './types/index';
|
|
3
3
|
export * from './errors/index';
|
|
4
4
|
export type { IBroker } from './interfaces/broker.interface';
|
|
5
5
|
export { InstrumentMaster } from './instruments/instrument-master';
|
|
6
|
+
export type { InstrumentMasterOptions } from './instruments/instrument-master';
|
|
6
7
|
export { WSManager } from './websocket/ws-manager';
|
|
7
8
|
export { SessionManager } from './session/session-manager';
|
|
8
9
|
export { ZerodhaBroker } from './brokers/zerodha/ZerodhaBroker';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,OAAO,UAAU,CAAC;AAG/B,cAAc,eAAe,CAAC;AAG9B,cAAc,gBAAgB,CAAC;AAG/B,YAAY,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAG7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAG3D,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAI1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAM7D;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAexD"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,OAAO,UAAU,CAAC;AAG/B,cAAc,eAAe,CAAC;AAG9B,cAAc,gBAAgB,CAAC;AAG/B,YAAY,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAG7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,YAAY,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAC/E,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAG3D,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAI1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAM7D;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAexD"}
|
package/dist/index.js
CHANGED
|
@@ -12626,10 +12626,10 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
12626
12626
|
}
|
|
12627
12627
|
}
|
|
12628
12628
|
var CanceledError$1 = CanceledError;
|
|
12629
|
-
function settle(
|
|
12629
|
+
function settle(resolve2, reject, response) {
|
|
12630
12630
|
const validateStatus = response.config.validateStatus;
|
|
12631
12631
|
if (!response.status || !validateStatus || validateStatus(response.status)) {
|
|
12632
|
-
|
|
12632
|
+
resolve2(response);
|
|
12633
12633
|
} else {
|
|
12634
12634
|
reject(new AxiosError$1("Request failed with status code " + response.status, [AxiosError$1.ERR_BAD_REQUEST, AxiosError$1.ERR_BAD_RESPONSE][Math.floor(response.status / 100) - 4], response.config, response.request, response));
|
|
12635
12635
|
}
|
|
@@ -13203,7 +13203,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
13203
13203
|
}
|
|
13204
13204
|
var isHttpAdapterSupported = typeof process !== "undefined" && utils$1.kindOf(process) === "process";
|
|
13205
13205
|
var wrapAsync = (asyncExecutor) => {
|
|
13206
|
-
return new Promise((
|
|
13206
|
+
return new Promise((resolve2, reject) => {
|
|
13207
13207
|
let onDone;
|
|
13208
13208
|
let isDone;
|
|
13209
13209
|
const done = (value, isRejected) => {
|
|
@@ -13214,7 +13214,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
13214
13214
|
};
|
|
13215
13215
|
const _resolve = (value) => {
|
|
13216
13216
|
done(value);
|
|
13217
|
-
|
|
13217
|
+
resolve2(value);
|
|
13218
13218
|
};
|
|
13219
13219
|
const _reject = (reason) => {
|
|
13220
13220
|
done(reason, true);
|
|
@@ -13266,7 +13266,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
13266
13266
|
}
|
|
13267
13267
|
};
|
|
13268
13268
|
var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) {
|
|
13269
|
-
return wrapAsync(async function dispatchHttpRequest(
|
|
13269
|
+
return wrapAsync(async function dispatchHttpRequest(resolve2, reject, onDone) {
|
|
13270
13270
|
let { data, lookup, family, httpVersion = 1, http2Options } = config;
|
|
13271
13271
|
const { responseType, responseEncoding } = config;
|
|
13272
13272
|
const method = config.method.toUpperCase();
|
|
@@ -13347,7 +13347,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
13347
13347
|
}
|
|
13348
13348
|
let convertedData;
|
|
13349
13349
|
if (method !== "GET") {
|
|
13350
|
-
return settle(
|
|
13350
|
+
return settle(resolve2, reject, {
|
|
13351
13351
|
status: 405,
|
|
13352
13352
|
statusText: "method not allowed",
|
|
13353
13353
|
headers: {},
|
|
@@ -13369,7 +13369,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
13369
13369
|
} else if (responseType === "stream") {
|
|
13370
13370
|
convertedData = stream__default["default"].Readable.from(convertedData);
|
|
13371
13371
|
}
|
|
13372
|
-
return settle(
|
|
13372
|
+
return settle(resolve2, reject, {
|
|
13373
13373
|
data: convertedData,
|
|
13374
13374
|
status: 200,
|
|
13375
13375
|
statusText: "OK",
|
|
@@ -13556,7 +13556,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
13556
13556
|
};
|
|
13557
13557
|
if (responseType === "stream") {
|
|
13558
13558
|
response.data = responseStream;
|
|
13559
|
-
settle(
|
|
13559
|
+
settle(resolve2, reject, response);
|
|
13560
13560
|
} else {
|
|
13561
13561
|
const responseBuffer = [];
|
|
13562
13562
|
let totalResponseBytes = 0;
|
|
@@ -13595,7 +13595,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
13595
13595
|
} catch (err) {
|
|
13596
13596
|
return reject(AxiosError$1.from(err, null, config, response.request, response));
|
|
13597
13597
|
}
|
|
13598
|
-
settle(
|
|
13598
|
+
settle(resolve2, reject, response);
|
|
13599
13599
|
});
|
|
13600
13600
|
}
|
|
13601
13601
|
abortEmitter.once("abort", (err) => {
|
|
@@ -13815,7 +13815,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
13815
13815
|
};
|
|
13816
13816
|
var isXHRAdapterSupported = typeof XMLHttpRequest !== "undefined";
|
|
13817
13817
|
var xhrAdapter = isXHRAdapterSupported && function(config) {
|
|
13818
|
-
return new Promise(function dispatchXhrRequest(
|
|
13818
|
+
return new Promise(function dispatchXhrRequest(resolve2, reject) {
|
|
13819
13819
|
const _config = resolveConfig(config);
|
|
13820
13820
|
let requestData = _config.data;
|
|
13821
13821
|
const requestHeaders = AxiosHeaders$1.from(_config.headers).normalize();
|
|
@@ -13847,7 +13847,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
13847
13847
|
request
|
|
13848
13848
|
};
|
|
13849
13849
|
settle(function _resolve(value) {
|
|
13850
|
-
|
|
13850
|
+
resolve2(value);
|
|
13851
13851
|
done();
|
|
13852
13852
|
}, function _reject(err) {
|
|
13853
13853
|
reject(err);
|
|
@@ -14198,8 +14198,8 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
14198
14198
|
responseType = responseType || "text";
|
|
14199
14199
|
let responseData = await resolvers[utils$1.findKey(resolvers, responseType) || "text"](response, config);
|
|
14200
14200
|
!isStreamResponse && unsubscribe && unsubscribe();
|
|
14201
|
-
return await new Promise((
|
|
14202
|
-
settle(
|
|
14201
|
+
return await new Promise((resolve2, reject) => {
|
|
14202
|
+
settle(resolve2, reject, {
|
|
14203
14203
|
data: responseData,
|
|
14204
14204
|
headers: AxiosHeaders$1.from(response.headers),
|
|
14205
14205
|
status: response.status,
|
|
@@ -14547,8 +14547,8 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
14547
14547
|
throw new TypeError("executor must be a function.");
|
|
14548
14548
|
}
|
|
14549
14549
|
let resolvePromise;
|
|
14550
|
-
this.promise = new Promise(function promiseExecutor(
|
|
14551
|
-
resolvePromise =
|
|
14550
|
+
this.promise = new Promise(function promiseExecutor(resolve2) {
|
|
14551
|
+
resolvePromise = resolve2;
|
|
14552
14552
|
});
|
|
14553
14553
|
const token = this;
|
|
14554
14554
|
this.promise.then((cancel) => {
|
|
@@ -14562,9 +14562,9 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
14562
14562
|
});
|
|
14563
14563
|
this.promise.then = (onfulfilled) => {
|
|
14564
14564
|
let _resolve;
|
|
14565
|
-
const promise = new Promise((
|
|
14566
|
-
token.subscribe(
|
|
14567
|
-
_resolve =
|
|
14565
|
+
const promise = new Promise((resolve2) => {
|
|
14566
|
+
token.subscribe(resolve2);
|
|
14567
|
+
_resolve = resolve2;
|
|
14568
14568
|
}).then(onfulfilled);
|
|
14569
14569
|
promise.cancel = function reject() {
|
|
14570
14570
|
token.unsubscribe(_resolve);
|
|
@@ -18454,7 +18454,7 @@ var require_connect2 = __commonJS((exports) => {
|
|
|
18454
18454
|
return `${this.default_login_uri}?api_key=${this.api_key}&v=3`;
|
|
18455
18455
|
}
|
|
18456
18456
|
generateSession(request_token, api_secret) {
|
|
18457
|
-
return new Promise((
|
|
18457
|
+
return new Promise((resolve2, reject) => {
|
|
18458
18458
|
const checksum = (0, sha256_1.default)(this.api_key + request_token + api_secret).toString();
|
|
18459
18459
|
const p = this._post("api.token", {
|
|
18460
18460
|
api_key: this.api_key,
|
|
@@ -18465,7 +18465,7 @@ var require_connect2 = __commonJS((exports) => {
|
|
|
18465
18465
|
if (resp && resp.access_token) {
|
|
18466
18466
|
this.setAccessToken(resp.access_token);
|
|
18467
18467
|
}
|
|
18468
|
-
return
|
|
18468
|
+
return resolve2(resp);
|
|
18469
18469
|
}).catch(function(err) {
|
|
18470
18470
|
return reject(err);
|
|
18471
18471
|
});
|
|
@@ -18478,7 +18478,7 @@ var require_connect2 = __commonJS((exports) => {
|
|
|
18478
18478
|
});
|
|
18479
18479
|
}
|
|
18480
18480
|
renewAccessToken(refresh_token, api_secret) {
|
|
18481
|
-
return new Promise((
|
|
18481
|
+
return new Promise((resolve2, reject) => {
|
|
18482
18482
|
const checksum = (0, sha256_1.default)(this.api_key + refresh_token + api_secret).toString();
|
|
18483
18483
|
const p = this._post("api.token.renew", {
|
|
18484
18484
|
api_key: this.api_key,
|
|
@@ -18489,7 +18489,7 @@ var require_connect2 = __commonJS((exports) => {
|
|
|
18489
18489
|
if (resp && resp.access_token) {
|
|
18490
18490
|
this.setAccessToken(resp.access_token);
|
|
18491
18491
|
}
|
|
18492
|
-
return
|
|
18492
|
+
return resolve2(resp);
|
|
18493
18493
|
}).catch(function(err) {
|
|
18494
18494
|
return reject(err);
|
|
18495
18495
|
});
|
|
@@ -19297,44 +19297,63 @@ class InstrumentNotFoundError extends BrokerError {
|
|
|
19297
19297
|
}
|
|
19298
19298
|
}
|
|
19299
19299
|
// src/instruments/instrument-master.ts
|
|
19300
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
19301
|
+
import { join, resolve } from "path";
|
|
19302
|
+
var DEFAULT_INSTRUMENT_PATH = "./InstrumentDump";
|
|
19303
|
+
|
|
19300
19304
|
class InstrumentMaster {
|
|
19301
19305
|
instruments = new Map;
|
|
19302
19306
|
tokenIndex = new Map;
|
|
19303
19307
|
syncMeta = new Map;
|
|
19304
19308
|
redis;
|
|
19309
|
+
instrumentFilePath;
|
|
19305
19310
|
constructor(options) {
|
|
19306
19311
|
this.redis = options?.redis ?? null;
|
|
19312
|
+
this.instrumentFilePath = resolve(options?.instrumentFilePath ?? DEFAULT_INSTRUMENT_PATH);
|
|
19307
19313
|
}
|
|
19308
19314
|
async syncBroker(brokerId, brokerInstruments, force = false) {
|
|
19309
19315
|
try {
|
|
19310
|
-
|
|
19311
|
-
|
|
19312
|
-
|
|
19313
|
-
|
|
19314
|
-
|
|
19315
|
-
|
|
19316
|
-
|
|
19317
|
-
|
|
19316
|
+
if (!force) {
|
|
19317
|
+
const diskEntries = this.loadFromDisk(brokerId);
|
|
19318
|
+
if (diskEntries) {
|
|
19319
|
+
this.clearBrokerEntries(brokerId);
|
|
19320
|
+
for (const entry of diskEntries) {
|
|
19321
|
+
this.mergeEntry(brokerId, entry);
|
|
19322
|
+
}
|
|
19323
|
+
const meta2 = {
|
|
19324
|
+
brokerId,
|
|
19325
|
+
lastSyncAt: Date.now(),
|
|
19326
|
+
instrumentCount: diskEntries.length,
|
|
19327
|
+
source: "disk"
|
|
19328
|
+
};
|
|
19329
|
+
this.syncMeta.set(brokerId, meta2);
|
|
19330
|
+
if (this.redis) {
|
|
19331
|
+
this.cacheInstrumentsToRedis();
|
|
19332
|
+
}
|
|
19333
|
+
return Ok(meta2);
|
|
19318
19334
|
}
|
|
19319
19335
|
}
|
|
19320
19336
|
this.clearBrokerEntries(brokerId);
|
|
19321
|
-
|
|
19337
|
+
const entries = [];
|
|
19322
19338
|
if (brokerInstruments.capabilities.bulkDump && brokerInstruments.streamDump) {
|
|
19323
19339
|
for await (const raw of brokerInstruments.streamDump()) {
|
|
19324
19340
|
const entry = brokerInstruments.normalize(raw);
|
|
19341
|
+
entries.push(entry);
|
|
19325
19342
|
this.mergeEntry(brokerId, entry);
|
|
19326
|
-
count++;
|
|
19327
19343
|
}
|
|
19328
19344
|
}
|
|
19345
|
+
if (this.redis) {
|
|
19346
|
+
this.cacheInstrumentsToRedis();
|
|
19347
|
+
} else {
|
|
19348
|
+
this.saveToDisk(brokerId, entries);
|
|
19349
|
+
}
|
|
19329
19350
|
const meta = {
|
|
19330
19351
|
brokerId,
|
|
19331
19352
|
lastSyncAt: Date.now(),
|
|
19332
|
-
instrumentCount:
|
|
19353
|
+
instrumentCount: entries.length,
|
|
19354
|
+
source: "network"
|
|
19333
19355
|
};
|
|
19334
19356
|
this.syncMeta.set(brokerId, meta);
|
|
19335
|
-
if (this.redis) {
|
|
19336
|
-
this.cacheInstrumentsToRedis();
|
|
19337
|
-
}
|
|
19338
19357
|
return Ok(meta);
|
|
19339
19358
|
} catch (err) {
|
|
19340
19359
|
return Err(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -19348,9 +19367,8 @@ class InstrumentMaster {
|
|
|
19348
19367
|
this.syncMeta.set(brokerId, {
|
|
19349
19368
|
brokerId,
|
|
19350
19369
|
lastSyncAt: Date.now(),
|
|
19351
|
-
|
|
19352
|
-
|
|
19353
|
-
instrumentCount: (existing?.instrumentCount ?? 0) + entries.length
|
|
19370
|
+
instrumentCount: (existing?.instrumentCount ?? 0) + entries.length,
|
|
19371
|
+
source: "network"
|
|
19354
19372
|
});
|
|
19355
19373
|
}
|
|
19356
19374
|
search(query, exchange, instrumentType, limit = 50) {
|
|
@@ -19396,6 +19414,35 @@ class InstrumentMaster {
|
|
|
19396
19414
|
return;
|
|
19397
19415
|
return this.instruments.get(entry.key);
|
|
19398
19416
|
}
|
|
19417
|
+
resolve(input) {
|
|
19418
|
+
const { exchange, symbol } = this.parseInput(input);
|
|
19419
|
+
if (exchange) {
|
|
19420
|
+
const key = this.makeKey(exchange, symbol);
|
|
19421
|
+
const inst = this.instruments.get(key);
|
|
19422
|
+
if (inst)
|
|
19423
|
+
return Ok(inst);
|
|
19424
|
+
return Err(new Error(`Symbol "${symbol}" not found on ${exchange}`));
|
|
19425
|
+
}
|
|
19426
|
+
const matches = [];
|
|
19427
|
+
for (const inst of this.instruments.values()) {
|
|
19428
|
+
if (inst.tradingSymbol === symbol)
|
|
19429
|
+
matches.push(inst);
|
|
19430
|
+
}
|
|
19431
|
+
if (matches.length === 0) {
|
|
19432
|
+
return Err(new Error(`Symbol "${symbol}" not found`));
|
|
19433
|
+
}
|
|
19434
|
+
if (matches.length === 1) {
|
|
19435
|
+
return Ok(matches[0]);
|
|
19436
|
+
}
|
|
19437
|
+
if (matches.length === 2) {
|
|
19438
|
+
const exchanges = new Set(matches.map((m) => m.exchange));
|
|
19439
|
+
if (exchanges.has("NSE") && exchanges.has("BSE")) {
|
|
19440
|
+
return Ok(matches.find((m) => m.exchange === "NSE"));
|
|
19441
|
+
}
|
|
19442
|
+
}
|
|
19443
|
+
const list = matches.slice(0, 5).map((m) => `${m.exchange}:${m.tradingSymbol}`).join(", ");
|
|
19444
|
+
return Err(new Error(`Multiple matches found. Specify exchange: ${list}`));
|
|
19445
|
+
}
|
|
19399
19446
|
async getOptionChainCached(underlying, expiry) {
|
|
19400
19447
|
const expiryDate = expiry.toISOString().slice(0, 10);
|
|
19401
19448
|
const cacheKey = `optchain:${underlying.toUpperCase()}:${expiryDate}`;
|
|
@@ -19442,6 +19489,9 @@ class InstrumentMaster {
|
|
|
19442
19489
|
get size() {
|
|
19443
19490
|
return this.instruments.size;
|
|
19444
19491
|
}
|
|
19492
|
+
get filePath() {
|
|
19493
|
+
return this.instrumentFilePath;
|
|
19494
|
+
}
|
|
19445
19495
|
getSyncMeta(brokerId) {
|
|
19446
19496
|
return this.syncMeta.get(brokerId);
|
|
19447
19497
|
}
|
|
@@ -19465,8 +19515,39 @@ class InstrumentMaster {
|
|
|
19465
19515
|
this.tokenIndex.clear();
|
|
19466
19516
|
this.syncMeta.clear();
|
|
19467
19517
|
}
|
|
19518
|
+
getDiskCachePath(brokerId) {
|
|
19519
|
+
return join(this.instrumentFilePath, `${brokerId}-instruments.json`);
|
|
19520
|
+
}
|
|
19521
|
+
ensureDirectory() {
|
|
19522
|
+
if (!existsSync(this.instrumentFilePath)) {
|
|
19523
|
+
mkdirSync(this.instrumentFilePath, { recursive: true });
|
|
19524
|
+
}
|
|
19525
|
+
}
|
|
19526
|
+
loadFromDisk(brokerId) {
|
|
19527
|
+
const filePath = this.getDiskCachePath(brokerId);
|
|
19528
|
+
try {
|
|
19529
|
+
if (!existsSync(filePath))
|
|
19530
|
+
return null;
|
|
19531
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
19532
|
+
const parsed = JSON.parse(raw);
|
|
19533
|
+
if (!Array.isArray(parsed))
|
|
19534
|
+
return null;
|
|
19535
|
+
return parsed;
|
|
19536
|
+
} catch {
|
|
19537
|
+
return null;
|
|
19538
|
+
}
|
|
19539
|
+
}
|
|
19540
|
+
saveToDisk(brokerId, entries) {
|
|
19541
|
+
try {
|
|
19542
|
+
this.ensureDirectory();
|
|
19543
|
+
const filePath = this.getDiskCachePath(brokerId);
|
|
19544
|
+
const stripped = entries.map(({ raw, ...rest }) => rest);
|
|
19545
|
+
writeFileSync(filePath, JSON.stringify(stripped), "utf-8");
|
|
19546
|
+
} catch {}
|
|
19547
|
+
}
|
|
19468
19548
|
mergeEntry(brokerId, entry) {
|
|
19469
|
-
const
|
|
19549
|
+
const canonical = this.buildCanonicalSymbol(entry.underlying, entry.instrumentType, entry.strike, entry.expiry);
|
|
19550
|
+
const key = this.makeKey(entry.exchange, canonical);
|
|
19470
19551
|
let instrument = this.instruments.get(key);
|
|
19471
19552
|
if (!instrument) {
|
|
19472
19553
|
instrument = {
|
|
@@ -19477,7 +19558,7 @@ class InstrumentMaster {
|
|
|
19477
19558
|
strike: entry.strike,
|
|
19478
19559
|
lotSize: entry.lotSize,
|
|
19479
19560
|
tickSize: entry.tickSize,
|
|
19480
|
-
tradingSymbol:
|
|
19561
|
+
tradingSymbol: canonical,
|
|
19481
19562
|
brokerTokens: {}
|
|
19482
19563
|
};
|
|
19483
19564
|
this.instruments.set(key, instrument);
|
|
@@ -19571,6 +19652,35 @@ class InstrumentMaster {
|
|
|
19571
19652
|
}
|
|
19572
19653
|
return true;
|
|
19573
19654
|
}
|
|
19655
|
+
formatExpiry(date) {
|
|
19656
|
+
const d = date instanceof Date ? date : new Date(date);
|
|
19657
|
+
const dd = String(d.getDate()).padStart(2, "0");
|
|
19658
|
+
const mmm = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"][d.getMonth()];
|
|
19659
|
+
const yy = String(d.getFullYear()).slice(-2);
|
|
19660
|
+
return `${dd}${mmm}${yy}`;
|
|
19661
|
+
}
|
|
19662
|
+
buildCanonicalSymbol(underlying, instrumentType, strike, expiry) {
|
|
19663
|
+
const name = underlying.toUpperCase();
|
|
19664
|
+
if (instrumentType === "EQ")
|
|
19665
|
+
return name;
|
|
19666
|
+
const exp = expiry ? this.formatExpiry(expiry) : "";
|
|
19667
|
+
if (instrumentType === "FUT")
|
|
19668
|
+
return `${name}-FUT-${exp}`;
|
|
19669
|
+
const s = strike != null ? String(strike) : "0";
|
|
19670
|
+
return `${name}-${s}-${instrumentType}-${exp}`;
|
|
19671
|
+
}
|
|
19672
|
+
parseInput(input) {
|
|
19673
|
+
const upper = input.trim().toUpperCase();
|
|
19674
|
+
const VALID = new Set(["NSE", "BSE", "NFO", "BFO", "MCX", "CDS"]);
|
|
19675
|
+
const idx = upper.indexOf(":");
|
|
19676
|
+
if (idx >= 2 && idx <= 3) {
|
|
19677
|
+
const prefix = upper.slice(0, idx);
|
|
19678
|
+
if (VALID.has(prefix)) {
|
|
19679
|
+
return { exchange: prefix, symbol: upper.slice(idx + 1) };
|
|
19680
|
+
}
|
|
19681
|
+
}
|
|
19682
|
+
return { symbol: upper };
|
|
19683
|
+
}
|
|
19574
19684
|
makeKey(exchange, tradingSymbol) {
|
|
19575
19685
|
return `${exchange}:${tradingSymbol}`;
|
|
19576
19686
|
}
|
|
@@ -23764,7 +23874,7 @@ class PaperBroker {
|
|
|
23764
23874
|
}
|
|
23765
23875
|
}
|
|
23766
23876
|
// src/index.ts
|
|
23767
|
-
var VERSION = "0.
|
|
23877
|
+
var VERSION = "0.2.0";
|
|
23768
23878
|
function createBroker(brokerId) {
|
|
23769
23879
|
switch (brokerId) {
|
|
23770
23880
|
case "zerodha":
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Maintains a Map<string, UnifiedInstrument> keyed by `{exchange}:{tradingSymbol}`.
|
|
5
5
|
* Supports:
|
|
6
|
-
* - syncBroker():
|
|
6
|
+
* - syncBroker(): download + cache instruments, or load from existing cache
|
|
7
7
|
* - search(): fuzzy text search with optional exchange/type filters
|
|
8
8
|
* - getBySymbol(): exact lookup by tradingSymbol + exchange (Redis -> memory)
|
|
9
9
|
* - getByToken(): lookup by broker-specific token
|
|
10
10
|
* - getOptionChain(): filter CE/PE instruments for a given underlying + expiry
|
|
11
|
+
* - Disk cache layer: saves normalized entries to `instrumentFilePath`
|
|
11
12
|
* - Optional Redis cache layer for distributed lookups
|
|
12
|
-
* - ETag tracking for change detection across re-syncs
|
|
13
13
|
*/
|
|
14
14
|
import type { BrokerId, Exchange, InstrumentType } from '../types/common';
|
|
15
15
|
import type { UnifiedInstrument, IBrokerInstruments, BrokerInstrumentEntry } from '../types/instruments';
|
|
@@ -23,13 +23,21 @@ export interface RedisClient {
|
|
|
23
23
|
interface SyncMeta {
|
|
24
24
|
brokerId: BrokerId;
|
|
25
25
|
lastSyncAt: number;
|
|
26
|
-
etag?: string;
|
|
27
|
-
lastModified?: string;
|
|
28
26
|
instrumentCount: number;
|
|
27
|
+
source: 'network' | 'disk' | 'redis';
|
|
29
28
|
}
|
|
30
29
|
export interface InstrumentMasterOptions {
|
|
31
30
|
/** Optional Redis client (ioredis-compatible) for distributed caching. */
|
|
32
31
|
redis?: RedisClient;
|
|
32
|
+
/**
|
|
33
|
+
* Path to store downloaded instrument dump files.
|
|
34
|
+
* Each broker's instruments are saved as `{brokerId}-instruments.json`.
|
|
35
|
+
*
|
|
36
|
+
* If not provided, defaults to `./InstrumentDump` relative to cwd.
|
|
37
|
+
* Make sure the path is valid and writable, or instrument dumps will
|
|
38
|
+
* be saved in the default `./InstrumentDump` folder.
|
|
39
|
+
*/
|
|
40
|
+
instrumentFilePath?: string;
|
|
33
41
|
}
|
|
34
42
|
export declare class InstrumentMaster {
|
|
35
43
|
/** Primary store: `{exchange}:{tradingSymbol}` -> UnifiedInstrument */
|
|
@@ -40,14 +48,24 @@ export declare class InstrumentMaster {
|
|
|
40
48
|
private syncMeta;
|
|
41
49
|
/** Optional Redis client for distributed cache. */
|
|
42
50
|
private readonly redis;
|
|
51
|
+
/** Resolved absolute path for instrument dump files. */
|
|
52
|
+
private readonly instrumentFilePath;
|
|
43
53
|
constructor(options?: InstrumentMasterOptions);
|
|
44
54
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
55
|
+
* Sync a broker's instruments into the unified index.
|
|
56
|
+
*
|
|
57
|
+
* By default, checks for existing cached data first:
|
|
58
|
+
* - Redis available → check Redis
|
|
59
|
+
* - No Redis → check disk (`{instrumentFilePath}/{brokerId}-instruments.json`)
|
|
60
|
+
* - No cache → download from network via broker's streamDump()
|
|
61
|
+
*
|
|
62
|
+
* After download, saves to Redis (if available) or disk (if no Redis).
|
|
63
|
+
* Pass `force = true` to skip cache and always re-download from network.
|
|
64
|
+
* Cache refresh is the user's responsibility — call with `force = true` when needed.
|
|
47
65
|
*
|
|
48
66
|
* @param brokerId - Identifier for the broker being synced
|
|
49
67
|
* @param brokerInstruments - The broker's IBrokerInstruments implementation
|
|
50
|
-
* @param force - Skip
|
|
68
|
+
* @param force - Skip cache check, force re-download from network
|
|
51
69
|
*/
|
|
52
70
|
syncBroker(brokerId: BrokerId, brokerInstruments: IBrokerInstruments, force?: boolean): Promise<Result<SyncMeta>>;
|
|
53
71
|
/**
|
|
@@ -78,25 +96,30 @@ export declare class InstrumentMaster {
|
|
|
78
96
|
* Get an instrument by a broker-specific token.
|
|
79
97
|
*/
|
|
80
98
|
getByToken(brokerToken: string, brokerId: BrokerId): UnifiedInstrument | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* Resolve a user-friendly input to a UnifiedInstrument.
|
|
101
|
+
* Accepts formats like `"TATAMOTORS"`, `"NSE:TATAMOTORS"`, `"NIFTY-24000-CE-27FEB26"`.
|
|
102
|
+
*
|
|
103
|
+
* When no exchange is specified and the symbol exists on both NSE and BSE only,
|
|
104
|
+
* NSE is preferred. If multiple matches span other exchanges, an error is returned
|
|
105
|
+
* listing them so the caller can be explicit.
|
|
106
|
+
*/
|
|
107
|
+
resolve(input: string): Result<UnifiedInstrument>;
|
|
81
108
|
/**
|
|
82
109
|
* Get all CE and PE instruments for a given underlying and expiry date.
|
|
83
110
|
* Returns instruments sorted by strike price.
|
|
84
111
|
* Results are cached in Redis (10 min TTL) when available.
|
|
85
|
-
*
|
|
86
|
-
* @param underlying - The underlying symbol (e.g. "NIFTY", "BANKNIFTY")
|
|
87
|
-
* @param expiry - The expiry date to match
|
|
88
112
|
*/
|
|
89
113
|
getOptionChainCached(underlying: string, expiry: Date): Promise<UnifiedInstrument[]>;
|
|
90
114
|
/**
|
|
91
115
|
* Get all CE and PE instruments for a given underlying and expiry date.
|
|
92
116
|
* Returns instruments sorted by strike price (in-memory only).
|
|
93
|
-
*
|
|
94
|
-
* @param underlying - The underlying symbol (e.g. "NIFTY", "BANKNIFTY")
|
|
95
|
-
* @param expiry - The expiry date to match
|
|
96
117
|
*/
|
|
97
118
|
getOptionChain(underlying: string, expiry: Date): UnifiedInstrument[];
|
|
98
119
|
/** Total number of instruments in the index. */
|
|
99
120
|
get size(): number;
|
|
121
|
+
/** Get the resolved instrument file path. */
|
|
122
|
+
get filePath(): string;
|
|
100
123
|
/** Get sync metadata for a specific broker. */
|
|
101
124
|
getSyncMeta(brokerId: BrokerId): SyncMeta | undefined;
|
|
102
125
|
/** Get sync metadata for all brokers. */
|
|
@@ -105,8 +128,25 @@ export declare class InstrumentMaster {
|
|
|
105
128
|
getAll(): UnifiedInstrument[];
|
|
106
129
|
/** Get all instruments for a specific exchange. */
|
|
107
130
|
getByExchange(exchange: Exchange): UnifiedInstrument[];
|
|
108
|
-
/** Clear the entire index. */
|
|
131
|
+
/** Clear the entire index (memory + optionally disk/Redis). */
|
|
109
132
|
clear(): void;
|
|
133
|
+
/**
|
|
134
|
+
* Get the disk cache file path for a broker.
|
|
135
|
+
*/
|
|
136
|
+
private getDiskCachePath;
|
|
137
|
+
/**
|
|
138
|
+
* Ensure the instrument dump directory exists.
|
|
139
|
+
*/
|
|
140
|
+
private ensureDirectory;
|
|
141
|
+
/**
|
|
142
|
+
* Load normalized instrument entries from disk cache.
|
|
143
|
+
* Returns null if file doesn't exist or is corrupt.
|
|
144
|
+
*/
|
|
145
|
+
private loadFromDisk;
|
|
146
|
+
/**
|
|
147
|
+
* Save normalized instrument entries to disk.
|
|
148
|
+
*/
|
|
149
|
+
private saveToDisk;
|
|
110
150
|
/**
|
|
111
151
|
* Merge a single BrokerInstrumentEntry into the unified index.
|
|
112
152
|
* If the instrument already exists (from another broker), the broker token
|
|
@@ -122,6 +162,12 @@ export declare class InstrumentMaster {
|
|
|
122
162
|
private scoreMatch;
|
|
123
163
|
/** Check if all characters of needle appear in haystack in order. */
|
|
124
164
|
private fuzzyMatch;
|
|
165
|
+
/** Format a Date as DDMMMYY (e.g. 27FEB26). */
|
|
166
|
+
private formatExpiry;
|
|
167
|
+
/** Build a canonical symbol: EQ → `TATA`, FUT → `NIFTY-FUT-27FEB26`, CE/PE → `NIFTY-24000-CE-27FEB26` */
|
|
168
|
+
private buildCanonicalSymbol;
|
|
169
|
+
/** Parse user input, splitting optional exchange prefix (e.g. `NSE:TATAMOTORS`). */
|
|
170
|
+
private parseInput;
|
|
125
171
|
/** Build the canonical map key for an instrument. */
|
|
126
172
|
private makeKey;
|
|
127
173
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrument-master.d.ts","sourceRoot":"","sources":["../../src/instruments/instrument-master.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"instrument-master.d.ts","sourceRoot":"","sources":["../../src/instruments/instrument-master.ts"],"names":[],"mappings":"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"}
|