laplace-api 4.3.2 → 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 +1 -1
- package/src/client/live-price.ts +68 -1
- package/src/client/stocks.ts +28 -0
- package/src/test/live-price.test.ts +58 -1
- package/src/test/stocks.test.ts +39 -0
package/package.json
CHANGED
package/src/client/live-price.ts
CHANGED
|
@@ -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 {
|
|
@@ -62,7 +71,7 @@ interface WebSocketUrlResponse {
|
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
interface WebSocketUsageResponse {
|
|
65
|
-
|
|
74
|
+
externalUserID: string;
|
|
66
75
|
firstConnectionTime: Date;
|
|
67
76
|
uniqueDeviceCount: number;
|
|
68
77
|
}
|
|
@@ -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(",")}®ion=${
|
|
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
|
}
|
package/src/client/stocks.ts
CHANGED
|
@@ -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
|
});
|
package/src/test/stocks.test.ts
CHANGED
|
@@ -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
|
});
|