laplace-api 4.3.3 → 4.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "laplace-api",
3
- "version": "4.3.3",
3
+ "version": "4.4.0",
4
4
  "description": "Client library for Laplace API for the US stock market and BIST (Istanbul stock market) fundamental financial data.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -26,6 +26,7 @@ export interface USStockPriceData {
26
26
 
27
27
  export type BISTStockStreamData = StreamMessage<BISTStockPriceData>;
28
28
  export type USStockStreamData = StreamMessage<USStockPriceData>;
29
+ export type BISTBidAskStreamData = StreamMessage<BISTBidAskData>;
29
30
 
30
31
  export enum OrderbookLevelSide {
31
32
  Bid = "bid",
@@ -40,6 +41,13 @@ export interface OrderbookLevel {
40
41
  side: OrderbookLevelSide;
41
42
  }
42
43
 
44
+ export interface BISTBidAskData {
45
+ d: string;
46
+ s: string;
47
+ ask: number;
48
+ bid: number;
49
+ }
50
+
43
51
  export interface OrderbookDeletedLevel {
44
52
  level: number;
45
53
  side: OrderbookLevelSide;
@@ -55,6 +63,7 @@ export enum PriceDataType {
55
63
  Live = "live",
56
64
  Delayed = "delayed",
57
65
  Orderbook = "orderbook",
66
+ Bids = "bids",
58
67
  }
59
68
 
60
69
  interface WebSocketUrlResponse {
@@ -87,6 +96,13 @@ interface UpdateUserDetailsParams {
87
96
  active: boolean;
88
97
  }
89
98
 
99
+ export interface SendWebsocketEventRequest {
100
+ externalUserID?: string;
101
+ event: Record<string, any>;
102
+ transient?: boolean;
103
+ broadCastToAll?: boolean;
104
+ }
105
+
90
106
  export interface ILivePriceClient<T> {
91
107
  close(): void;
92
108
  receive(): AsyncIterable<T>;
@@ -155,6 +171,13 @@ class LivePriceClientImpl<T> implements ILivePriceClient<T> {
155
171
  this.region
156
172
  }&stream=${streamId}`;
157
173
  break;
174
+ case PriceDataType.Bids:
175
+ url = `${
176
+ this.client["baseUrl"]
177
+ }/api/v1/stock/price/bids?filter=${symbols.join(",")}&region=${
178
+ this.region
179
+ }&stream=${streamId}`;
180
+ break;
158
181
  }
159
182
 
160
183
  const { events, cancel } = this.client.sendSSERequest<T>(url);
@@ -217,6 +240,27 @@ function getOrderbook<T>(
217
240
  return orderbookClient;
218
241
  }
219
242
 
243
+ function getBidAsk<T>(
244
+ client: Client,
245
+ symbols: string[],
246
+ region: Region
247
+ ): ILivePriceClient<T> {
248
+ if (!client) {
249
+ throw new Error("Client cannot be null");
250
+ }
251
+
252
+ const bidAskClient = new LivePriceClientImpl<T>(
253
+ client,
254
+ region,
255
+ PriceDataType.Bids
256
+ );
257
+ bidAskClient.subscribe(symbols).catch((error) => {
258
+ console.error("Failed to initialize bist bid ask client", error);
259
+ });
260
+
261
+ return bidAskClient;
262
+ }
263
+
220
264
  export function getLivePriceForBIST(
221
265
  client: Client,
222
266
  symbols: string[]
@@ -245,6 +289,13 @@ export function getOrderbookForBIST(
245
289
  return getOrderbook<OrderbookLiveData>(client, symbols, Region.Tr);
246
290
  }
247
291
 
292
+ export function getBidAskForBIST(
293
+ client: Client,
294
+ symbols: string[]
295
+ ): ILivePriceClient<BISTBidAskStreamData> {
296
+ return getBidAsk<BISTBidAskStreamData>(client, symbols, Region.Tr);
297
+ }
298
+
248
299
  export class LivePriceClient extends Client {
249
300
  getLivePriceForBIST(symbols: string[]): ILivePriceClient<BISTStockStreamData> {
250
301
  return getLivePriceForBIST(this, symbols);
@@ -264,6 +315,10 @@ export class LivePriceClient extends Client {
264
315
  return getOrderbookForBIST(this, symbols);
265
316
  }
266
317
 
318
+ getBidAskForBIST(symbols: string[]): ILivePriceClient<BISTBidAskStreamData> {
319
+ return getBidAskForBIST(this, symbols);
320
+ }
321
+
267
322
  async getClientWebsocketUrl(
268
323
  externalUserId: string,
269
324
  feeds: LivePriceFeed[]
@@ -301,4 +356,16 @@ export class LivePriceClient extends Client {
301
356
 
302
357
  return response;
303
358
  }
359
+
360
+ async sendWebsocketEvent(
361
+ request: SendWebsocketEventRequest
362
+ ): Promise<void> {
363
+ const url = new URL(`${this["baseUrl"]}/api/v1/ws/event`);
364
+
365
+ await this.sendRequest<void>({
366
+ method: "POST",
367
+ url: url.toString(),
368
+ data: request,
369
+ });
370
+ }
304
371
  }
@@ -149,6 +149,15 @@ export interface MarketState {
149
149
  stockSymbol?: string | null;
150
150
  }
151
151
 
152
+ export interface GenerateChartImageRequest {
153
+ symbol: string;
154
+ period?: HistoricalPricePeriod;
155
+ region: Region;
156
+ resolution?: HistoricalPriceInterval;
157
+ indicators?: string[];
158
+ chartType?: number;
159
+ }
160
+
152
161
  export class StockClient extends Client {
153
162
  async getAllStocks(region: Region, page: number|null = null, pageSize: number|null = null): Promise<Stock[]> {
154
163
  return this.sendRequest<Stock[]>({
@@ -278,4 +287,23 @@ export class StockClient extends Client {
278
287
  url: `/api/v1/state/${symbol}`,
279
288
  });
280
289
  }
290
+
291
+ async getStockChartImage(request: GenerateChartImageRequest): Promise<Blob> {
292
+ const params: Partial<GenerateChartImageRequest> = {
293
+ symbol: request.symbol,
294
+ region: request.region,
295
+ };
296
+
297
+ if (request.period) params.period = request.period;
298
+ if (request.resolution) params.resolution = request.resolution;
299
+ if (request.indicators) params.indicators = request.indicators;
300
+ if (request.chartType != null) params.chartType = request.chartType;
301
+
302
+ return this.sendRequest<Blob>({
303
+ method: 'GET',
304
+ url: '/api/v1/stock/chart',
305
+ params,
306
+ responseType: 'blob',
307
+ });
308
+ }
281
309
  }
@@ -1,7 +1,7 @@
1
1
  import { Logger } from "winston";
2
2
  import { LaplaceConfiguration } from "../utilities/configuration";
3
3
  import "./client_test_suite";
4
- import { BISTStockStreamData, LivePriceClient, OrderbookLiveData } from "../client/live-price";
4
+ import { BISTBidAskStreamData, BISTStockStreamData, LivePriceClient, OrderbookLiveData } from "../client/live-price";
5
5
 
6
6
  describe("LivePrice", () => {
7
7
  let client: LivePriceClient;
@@ -474,4 +474,61 @@ describe("LivePrice", () => {
474
474
  TEST_CONSTANTS.JEST_TIMEOUT
475
475
  );
476
476
  });
477
+
478
+ describe("GetBidAskForBIST", () => {
479
+ it(
480
+ "should receive BIST bid/ask data",
481
+ async () => {
482
+ const symbols = ["AKBNK"];
483
+ let receivedData: BISTBidAskStreamData | null = null;
484
+ let receivedError: Error | null = null;
485
+
486
+ const lc = client.getBidAskForBIST(symbols);
487
+ activeConnections.push(lc);
488
+
489
+ try {
490
+ const receiveChan = lc.receive();
491
+
492
+ const timeoutPromise = new Promise<void>((_, reject) => {
493
+ const timeout = setTimeout(
494
+ () => reject(new Error("Timeout waiting for bid/ask data")),
495
+ TEST_CONSTANTS.MAIN_TIMEOUT
496
+ );
497
+ activeTimeouts.push(timeout);
498
+ });
499
+
500
+ const dataPromise = (async () => {
501
+ try {
502
+ for await (const data of receiveChan) {
503
+ receivedData = data;
504
+ break;
505
+ }
506
+ } catch (error) {
507
+ console.log("Error in bid/ask data stream:", error);
508
+ }
509
+ })();
510
+
511
+ await Promise.race([dataPromise, timeoutPromise]);
512
+
513
+ if (receivedData != null) {
514
+ const tempReceivedData = (receivedData as BISTBidAskStreamData).d;
515
+ console.log("Received BIST bid/ask data:", tempReceivedData);
516
+ expect(tempReceivedData.s).toBeDefined();
517
+ expect(typeof tempReceivedData.s).toBe("string");
518
+ expect(typeof tempReceivedData.d).toBe("string");
519
+ expect(typeof tempReceivedData.ask).toBe("number");
520
+ expect(typeof tempReceivedData.bid).toBe("number");
521
+ } else {
522
+ console.log("Timeout waiting for BIST bid/ask data");
523
+ }
524
+ } catch (error) {
525
+ receivedError = error as Error;
526
+ console.log("Received bid/ask error:", receivedError.message);
527
+ } finally {
528
+ lc.close();
529
+ }
530
+ },
531
+ TEST_CONSTANTS.JEST_TIMEOUT
532
+ );
533
+ });
477
534
  });
@@ -180,6 +180,8 @@ const mockSingleMarketState: MarketState = {
180
180
  stockSymbol: "TUPRS"
181
181
  };
182
182
 
183
+ const mockChartImageBlob = new Blob(['mock chart image data'], { type: 'image/png' });
184
+
183
185
  describe("Stocks Client", () => {
184
186
  let client: StockClient;
185
187
 
@@ -486,6 +488,19 @@ describe("Stocks Client", () => {
486
488
  }
487
489
  });
488
490
  });
491
+
492
+ describe("getStockChartImage", () => {
493
+ test("should return chart image blob", async () => {
494
+ const resp = await client.getStockChartImage({
495
+ symbol: "TUPRS",
496
+ region: Region.Tr,
497
+ });
498
+
499
+ expect(resp).toBeDefined();
500
+ expect(resp).toBeInstanceOf(Blob);
501
+ expect(resp.size).toBeGreaterThan(0);
502
+ });
503
+ });
489
504
  });
490
505
 
491
506
  describe("Mock Tests", () => {
@@ -823,5 +838,29 @@ describe("Stocks Client", () => {
823
838
  .rejects.toThrow("Market state not found");
824
839
  });
825
840
  });
841
+
842
+ describe("getStockChartImage", () => {
843
+ test("should return chart image with mock data", async () => {
844
+ jest.spyOn(client, 'getStockChartImage').mockResolvedValue(mockChartImageBlob);
845
+
846
+ const resp = await client.getStockChartImage({
847
+ symbol: "TUPRS",
848
+ region: Region.Tr,
849
+ });
850
+
851
+ expect(resp).toBeDefined();
852
+ expect(resp).toBeInstanceOf(Blob);
853
+ expect(resp.type).toBe('image/png');
854
+ });
855
+
856
+ test("should handle API errors for chart image", async () => {
857
+ jest.spyOn(client, 'getStockChartImage').mockRejectedValue(new Error("Failed to generate chart"));
858
+
859
+ await expect(client.getStockChartImage({
860
+ symbol: "INVALID",
861
+ region: Region.Tr
862
+ })).rejects.toThrow("Failed to generate chart");
863
+ });
864
+ });
826
865
  });
827
866
  });