laplace-api 1.0.1 → 1.0.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.
- package/package.json +1 -1
- package/src/client/client.ts +48 -37
- package/src/client/collections.ts +4 -0
- package/src/client/custom_theme.ts +93 -0
- package/src/client/errors.ts +75 -0
- package/src/client/financial_fundamentals.ts +9 -12
- package/src/client/financial_ratios.ts +162 -0
- package/src/client/live_price.ts +2 -7
- package/src/test/client.test.ts +35 -20
- package/src/test/custom_theme.test.ts +123 -0
- package/src/test/financial_fundamentals.test.ts +17 -4
- package/src/test/financial_ratios.test.ts +51 -0
- package/src/test/live_price +57 -0
- package/src/test/live_price.test.ts +0 -58
package/package.json
CHANGED
package/src/client/client.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
|
|
2
2
|
import { Logger } from 'winston';
|
|
3
3
|
import { LaplaceConfiguration } from '../utilities/configuration';
|
|
4
|
-
import {
|
|
4
|
+
import { LaplaceHTTPError, WrapError } from './errors';
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
export class Client {
|
|
@@ -31,53 +31,64 @@ export class Client {
|
|
|
31
31
|
return response.data;
|
|
32
32
|
} catch (error) {
|
|
33
33
|
if (axios.isAxiosError(error) && error.response) {
|
|
34
|
-
|
|
34
|
+
const httpError = new LaplaceHTTPError(
|
|
35
|
+
error.response.status,
|
|
36
|
+
JSON.stringify(error.response.data)
|
|
37
|
+
);
|
|
38
|
+
throw WrapError(httpError);
|
|
35
39
|
}
|
|
36
40
|
throw error;
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
Accept: 'text/event-stream',
|
|
44
|
-
'Cache-Control': 'no-cache',
|
|
45
|
-
Connection: 'keep-alive',
|
|
46
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
47
|
-
},
|
|
48
|
-
});
|
|
44
|
+
sendSSERequest<T>(url: string): { events: AsyncIterable<T>, cancel: () => void } {
|
|
45
|
+
const source = axios.CancelToken.source();
|
|
46
|
+
var apiKey = this.apiKey;
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
reject(new Error(`Error parsing event data: ${error}`));
|
|
61
|
-
}
|
|
62
|
-
};
|
|
48
|
+
const events: AsyncIterable<T> = {
|
|
49
|
+
async *[Symbol.asyncIterator]() {
|
|
50
|
+
try {
|
|
51
|
+
const response = await axios.get(url, {
|
|
52
|
+
headers: {
|
|
53
|
+
Authorization: `Bearer ${apiKey}`,
|
|
54
|
+
},
|
|
55
|
+
responseType: 'stream',
|
|
56
|
+
cancelToken: source.token,
|
|
57
|
+
});
|
|
63
58
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
const reader = response.data;
|
|
60
|
+
const decoder = new TextDecoder();
|
|
61
|
+
|
|
62
|
+
for await (const chunk of reader) {
|
|
63
|
+
const text = decoder.decode(chunk);
|
|
64
|
+
const lines = text.split('\n').filter(line => line.trim() !== '');
|
|
65
|
+
|
|
66
|
+
for (const line of lines) {
|
|
67
|
+
if (line.startsWith('data: ')) {
|
|
68
|
+
const data = line.slice(6);
|
|
69
|
+
try {
|
|
70
|
+
const parsedData = JSON.parse(data) as T;
|
|
71
|
+
yield parsedData;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error(`Error parsing event data: ${error}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
68
76
|
}
|
|
69
|
-
} finally {
|
|
70
|
-
eventSource.close();
|
|
71
77
|
}
|
|
72
|
-
}
|
|
73
|
-
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (!axios.isCancel(error)) {
|
|
80
|
+
console.error(`SSE error: ${error}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
74
85
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
const cancel = () => {
|
|
87
|
+
source.cancel('Request cancelled by user');
|
|
88
|
+
};
|
|
78
89
|
|
|
79
|
-
|
|
80
|
-
|
|
90
|
+
return { events, cancel };
|
|
91
|
+
}
|
|
81
92
|
}
|
|
82
93
|
|
|
83
94
|
export function createClient(cfg: LaplaceConfiguration, logger: Logger): Client {
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Client } from './client';
|
|
2
|
+
import { Collection, CollectionDetail, CollectionType, Region, Locale, SortBy } from './collections';
|
|
3
|
+
|
|
4
|
+
export enum CollectionStatus {
|
|
5
|
+
Active = 'active',
|
|
6
|
+
Inactive = 'inactive',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface LocaleString {
|
|
10
|
+
[key: string]: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CreateCustomThemeParams {
|
|
14
|
+
title: LocaleString;
|
|
15
|
+
description?: LocaleString;
|
|
16
|
+
region?: Region[];
|
|
17
|
+
image_url?: string;
|
|
18
|
+
image?: string;
|
|
19
|
+
avatar_url?: string;
|
|
20
|
+
stocks: string[]; // Using string instead of ObjectID
|
|
21
|
+
order?: number;
|
|
22
|
+
status: CollectionStatus;
|
|
23
|
+
meta_data?: Record<string, any>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface UpdateCustomThemeParams {
|
|
27
|
+
title?: LocaleString;
|
|
28
|
+
description?: LocaleString;
|
|
29
|
+
image_url?: string;
|
|
30
|
+
image?: string;
|
|
31
|
+
avatar_url?: string;
|
|
32
|
+
stockIds?: string[]; // Using string instead of ObjectID
|
|
33
|
+
status?: CollectionStatus;
|
|
34
|
+
meta_data?: Record<string, any>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class CustomThemeClient extends Client {
|
|
38
|
+
private async getAllCollectionsPrivate(collectionType: CollectionType, locale: Locale): Promise<Collection[]> {
|
|
39
|
+
return this.sendRequest<Collection[]>({
|
|
40
|
+
method: 'GET',
|
|
41
|
+
url: `/api/v1/${collectionType}`,
|
|
42
|
+
params: { locale },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private async getCollectionDetailPrivate(id: string, collectionType: CollectionType, locale: Locale, sortBy: SortBy | null): Promise<CollectionDetail> {
|
|
47
|
+
var params = {}
|
|
48
|
+
if (sortBy) {
|
|
49
|
+
params = { locale, sortBy };
|
|
50
|
+
} else {
|
|
51
|
+
params = { locale };
|
|
52
|
+
}
|
|
53
|
+
return this.sendRequest<CollectionDetail>({
|
|
54
|
+
method: 'GET',
|
|
55
|
+
url: `/api/v1/${collectionType}/${id}`,
|
|
56
|
+
params: params,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async getAllCustomThemes(locale: Locale): Promise<Collection[]> {
|
|
62
|
+
return this.getAllCollectionsPrivate(CollectionType.CustomTheme, locale);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async getCustomThemeDetail(id: string, locale: Locale, sortBy: SortBy | null): Promise<CollectionDetail> {
|
|
66
|
+
return this.getCollectionDetailPrivate(id, CollectionType.CustomTheme, locale, sortBy);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async createCustomTheme(params: CreateCustomThemeParams): Promise<string> {
|
|
70
|
+
const response = await this.sendRequest<string>({
|
|
71
|
+
method: 'POST',
|
|
72
|
+
url: `/api/v1/custom-theme`,
|
|
73
|
+
data: params,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return response;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async updateCustomTheme(id: string, params: UpdateCustomThemeParams): Promise<void> {
|
|
80
|
+
await this.sendRequest<void>({
|
|
81
|
+
method: 'PATCH',
|
|
82
|
+
url: `/api/v1/custom-theme/${id}`,
|
|
83
|
+
data: params,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async deleteCustomTheme(id: string): Promise<void> {
|
|
88
|
+
await this.sendRequest<void>({
|
|
89
|
+
method: 'DELETE',
|
|
90
|
+
url: `/api/v1/custom-theme/${id}`,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export class LaplaceError extends Error {
|
|
2
|
+
constructor(message: string) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'LaplaceError';
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const ErrYouDoNotHaveAccessToEndpoint = new LaplaceError('you do not have access to this endpoint');
|
|
9
|
+
export const ErrLimitExceeded = new LaplaceError('limit exceeded');
|
|
10
|
+
export const ErrEndpointIsNotActive = new LaplaceError('endpoint is not active');
|
|
11
|
+
export const ErrInvalidToken = new LaplaceError('invalid token');
|
|
12
|
+
export const ErrInvalidID = new LaplaceError('invalid object id');
|
|
13
|
+
|
|
14
|
+
export class LaplaceHTTPError extends Error {
|
|
15
|
+
httpStatus: number;
|
|
16
|
+
message: string;
|
|
17
|
+
internalError?: Error;
|
|
18
|
+
|
|
19
|
+
constructor(httpStatus: number, message: string, internalError?: Error) {
|
|
20
|
+
super(`${httpStatus}: ${message}${internalError ? ` (${internalError.message})` : ''}`);
|
|
21
|
+
this.name = 'LaplaceHTTPError';
|
|
22
|
+
this.httpStatus = httpStatus;
|
|
23
|
+
this.message = message;
|
|
24
|
+
this.internalError = internalError;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
withInternalError(err: Error): LaplaceHTTPError {
|
|
28
|
+
this.internalError = err;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
cause(): Error {
|
|
33
|
+
return this.internalError || this;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function WrapError(err: Error): Error {
|
|
38
|
+
if (err instanceof LaplaceHTTPError) {
|
|
39
|
+
getLaplaceError(err);
|
|
40
|
+
return err;
|
|
41
|
+
}
|
|
42
|
+
return err;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getLaplaceError(httpErr: LaplaceHTTPError): void {
|
|
46
|
+
switch (httpErr.httpStatus) {
|
|
47
|
+
case 403:
|
|
48
|
+
switch (httpErr.message) {
|
|
49
|
+
case '{"message":"you don\'t have access to this endpoint"}\n':
|
|
50
|
+
httpErr.withInternalError(ErrYouDoNotHaveAccessToEndpoint);
|
|
51
|
+
break;
|
|
52
|
+
case '{"message":"endpoint is not active"}\n':
|
|
53
|
+
httpErr.withInternalError(ErrEndpointIsNotActive);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
if (httpErr.message.includes('limit exceeded')) {
|
|
57
|
+
httpErr.withInternalError(ErrLimitExceeded);
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
case 400:
|
|
61
|
+
if (httpErr.message === '{"message":"invalid id"}\n') {
|
|
62
|
+
httpErr.withInternalError(ErrInvalidID);
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
case 401:
|
|
66
|
+
if (httpErr.message === '{"message":"this token is not valid"}\n') {
|
|
67
|
+
httpErr.withInternalError(ErrInvalidToken);
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function HttpError(httpStatus: number, message: string, ...args: any[]): LaplaceHTTPError {
|
|
74
|
+
return new LaplaceHTTPError(httpStatus, message.replace(/%s/g, () => args.shift()));
|
|
75
|
+
}
|
|
@@ -19,8 +19,8 @@ export interface StockStats {
|
|
|
19
19
|
pbRatio: number;
|
|
20
20
|
yearLow: number;
|
|
21
21
|
yearHigh: number;
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
'3Year': number;
|
|
23
|
+
'5Year': number;
|
|
24
24
|
symbol: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -42,38 +42,35 @@ export interface TopMover {
|
|
|
42
42
|
symbol: string;
|
|
43
43
|
percentChange: number;
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
export class FinancialFundamentalsClient {
|
|
47
|
-
constructor(private client: Client) {}
|
|
48
|
-
|
|
45
|
+
export class FinancialFundamentalsClient extends Client {
|
|
49
46
|
async getStockDividends(symbol: string, region: Region): Promise<StockDividend[]> {
|
|
50
|
-
const url = new URL(`${this
|
|
47
|
+
const url = new URL(`${this['baseUrl']}/api/v1/stock/dividends`);
|
|
51
48
|
url.searchParams.append('symbol', symbol);
|
|
52
49
|
url.searchParams.append('region', region);
|
|
53
50
|
|
|
54
|
-
return this.
|
|
51
|
+
return this.sendRequest<StockDividend[]>({
|
|
55
52
|
method: 'GET',
|
|
56
53
|
url: url.toString(),
|
|
57
54
|
});
|
|
58
55
|
}
|
|
59
56
|
|
|
60
57
|
async getStockStats(symbols: string[], keys: StockStatsKey[], region: Region): Promise<StockStats[]> {
|
|
61
|
-
const url = new URL(`${this
|
|
58
|
+
const url = new URL(`${this['baseUrl']}/api/v1/stock/stats`);
|
|
62
59
|
url.searchParams.append('symbols', symbols.join(','));
|
|
63
60
|
url.searchParams.append('region', region);
|
|
64
61
|
url.searchParams.append('keys', keys.join(','));
|
|
65
62
|
|
|
66
|
-
return this.
|
|
63
|
+
return this.sendRequest<StockStats[]>({
|
|
67
64
|
method: 'GET',
|
|
68
65
|
url: url.toString(),
|
|
69
66
|
});
|
|
70
67
|
}
|
|
71
68
|
|
|
72
69
|
async getTopMovers(region: Region): Promise<TopMover[]> {
|
|
73
|
-
const url = new URL(`${this
|
|
70
|
+
const url = new URL(`${this['baseUrl']}/api/v1/stock/top-movers`);
|
|
74
71
|
url.searchParams.append('region', region);
|
|
75
72
|
|
|
76
|
-
return this.
|
|
73
|
+
return this.sendRequest<TopMover[]>({
|
|
77
74
|
method: 'GET',
|
|
78
75
|
url: url.toString(),
|
|
79
76
|
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { Client } from './client';
|
|
2
|
+
import { Region, Locale } from './collections';
|
|
3
|
+
|
|
4
|
+
export interface StockSectorFinancialRatioComparison {
|
|
5
|
+
metric_name: string;
|
|
6
|
+
normalizedValue: number;
|
|
7
|
+
details: StockSectorFinancialRatioComparisonDetail[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface StockSectorFinancialRatioComparisonDetail {
|
|
11
|
+
slug: string;
|
|
12
|
+
value: number;
|
|
13
|
+
sectorAverage: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface StockHistoricalRatios {
|
|
17
|
+
symbol: string;
|
|
18
|
+
data: StockHistoricalRatiosData[];
|
|
19
|
+
formatting: Record<string, StockHistoricalRatiosFormatting>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface StockHistoricalRatiosData {
|
|
23
|
+
fiscalYear: number;
|
|
24
|
+
fiscalQuarter: number;
|
|
25
|
+
values: Record<string, StockHistoricalRatiosValue>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface StockHistoricalRatiosValue {
|
|
29
|
+
value: number;
|
|
30
|
+
sectorAverage: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface StockHistoricalRatiosFormatting {
|
|
34
|
+
slug: string;
|
|
35
|
+
precision: number;
|
|
36
|
+
multiplier: number;
|
|
37
|
+
suffix: string;
|
|
38
|
+
prefix: string;
|
|
39
|
+
interval: string;
|
|
40
|
+
description: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export enum HistoricalRatiosKey {
|
|
44
|
+
PriceToEarningsRatio = 'pe-ratio',
|
|
45
|
+
ReturnOnEquity = 'roe',
|
|
46
|
+
ReturnOnAssets = 'roa',
|
|
47
|
+
ReturnOnCapital = 'roic',
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface StockHistoricalRatiosDescription {
|
|
51
|
+
slug: string;
|
|
52
|
+
name: string;
|
|
53
|
+
suffix: string;
|
|
54
|
+
prefix: string;
|
|
55
|
+
display: boolean;
|
|
56
|
+
precision: number;
|
|
57
|
+
multiplier: number;
|
|
58
|
+
description: string;
|
|
59
|
+
interval: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface HistoricalFinancialSheets {
|
|
63
|
+
sheets: HistoricalFinancialSheet[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface HistoricalFinancialSheet {
|
|
67
|
+
period: string;
|
|
68
|
+
rows: HistoricalFinancialSheetRow[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface HistoricalFinancialSheetRow {
|
|
72
|
+
description: string;
|
|
73
|
+
value: number;
|
|
74
|
+
lineCodeId: number;
|
|
75
|
+
indentLevel: number;
|
|
76
|
+
firstAncestorLineCodeId: number;
|
|
77
|
+
sectionLineCodeId: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export enum FinancialSheetType {
|
|
81
|
+
IncomeStatement = 'incomeStatement',
|
|
82
|
+
BalanceSheet = 'balanceSheet',
|
|
83
|
+
CashFlow = 'cashFlowStatement',
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export enum FinancialSheetPeriod {
|
|
87
|
+
Annual = 'annual',
|
|
88
|
+
Quarterly = 'quarterly',
|
|
89
|
+
Cumulative = 'cumulative',
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export enum Currency {
|
|
93
|
+
USD = 'USD',
|
|
94
|
+
TRY = 'TRY',
|
|
95
|
+
EUR = 'EUR',
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface FinancialSheetDate {
|
|
99
|
+
day: number;
|
|
100
|
+
month: number;
|
|
101
|
+
year: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export class FinancialClient extends Client {
|
|
105
|
+
async getFinancialRatioComparison(symbol: string, region: Region): Promise<StockSectorFinancialRatioComparison[]> {
|
|
106
|
+
const url = new URL(`${this['baseUrl']}/api/v1/stock/financial-ratio-comparison`);
|
|
107
|
+
url.searchParams.append('symbol', symbol);
|
|
108
|
+
url.searchParams.append('region', region);
|
|
109
|
+
|
|
110
|
+
return this.sendRequest<StockSectorFinancialRatioComparison[]>({
|
|
111
|
+
method: 'GET',
|
|
112
|
+
url: url.toString(),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async getHistoricalRatios(symbol: string, keys: HistoricalRatiosKey[], region: Region): Promise<StockHistoricalRatios> {
|
|
117
|
+
const url = new URL(`${this['baseUrl']}/api/v1/stock/historical-ratios`);
|
|
118
|
+
url.searchParams.append('symbol', symbol);
|
|
119
|
+
url.searchParams.append('region', region);
|
|
120
|
+
url.searchParams.append('slugs', keys.join(','));
|
|
121
|
+
|
|
122
|
+
return this.sendRequest<StockHistoricalRatios>({
|
|
123
|
+
method: 'GET',
|
|
124
|
+
url: url.toString(),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async getHistoricalRatiosDescriptions(locale: Locale, region: Region): Promise<StockHistoricalRatiosDescription[]> {
|
|
129
|
+
const url = new URL(`${this['baseUrl']}/api/v1/stock/historical-ratios/descriptions`);
|
|
130
|
+
url.searchParams.append('locale', locale);
|
|
131
|
+
url.searchParams.append('region', region);
|
|
132
|
+
|
|
133
|
+
return this.sendRequest<StockHistoricalRatiosDescription[]>({
|
|
134
|
+
method: 'GET',
|
|
135
|
+
url: url.toString(),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async getHistoricalFinancialSheets(
|
|
140
|
+
symbol: string,
|
|
141
|
+
from: FinancialSheetDate,
|
|
142
|
+
to: FinancialSheetDate,
|
|
143
|
+
sheetType: FinancialSheetType,
|
|
144
|
+
period: FinancialSheetPeriod,
|
|
145
|
+
currency: Currency,
|
|
146
|
+
region: Region
|
|
147
|
+
): Promise<HistoricalFinancialSheets> {
|
|
148
|
+
const url = new URL(`${this['baseUrl']}/api/v1/stock/historical-financial-sheets`);
|
|
149
|
+
url.searchParams.append('symbol', symbol);
|
|
150
|
+
url.searchParams.append('from', `${from.year.toString().padStart(4, '0')}-${from.month.toString().padStart(2, '0')}-${from.day.toString().padStart(2, '0')}`);
|
|
151
|
+
url.searchParams.append('to', `${to.year.toString().padStart(4, '0')}-${to.month.toString().padStart(2, '0')}-${to.day.toString().padStart(2, '0')}`);
|
|
152
|
+
url.searchParams.append('sheetType', sheetType);
|
|
153
|
+
url.searchParams.append('periodType', period);
|
|
154
|
+
url.searchParams.append('currency', currency);
|
|
155
|
+
url.searchParams.append('region', region);
|
|
156
|
+
|
|
157
|
+
return this.sendRequest<HistoricalFinancialSheets>({
|
|
158
|
+
method: 'GET',
|
|
159
|
+
url: url.toString(),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
package/src/client/live_price.ts
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import { Client } from './client';
|
|
2
|
+
import { Region } from './collections';
|
|
2
3
|
import { v4 as uuidv4 } from 'uuid';
|
|
3
4
|
|
|
4
|
-
export enum Region {
|
|
5
|
-
BIST = 'BIST',
|
|
6
|
-
US = 'US',
|
|
7
|
-
// Add other regions as needed
|
|
8
|
-
}
|
|
9
|
-
|
|
10
5
|
async function* getLivePrice<T>(
|
|
11
6
|
client: Client,
|
|
12
7
|
symbols: string[],
|
|
13
8
|
region: Region
|
|
14
9
|
): AsyncGenerator<T, void, undefined> {
|
|
15
10
|
const streamId = uuidv4();
|
|
16
|
-
const url = `${client[
|
|
11
|
+
const url = `${client["baseUrl"]}/api/v1/stock/price/live?filter=${symbols.join(',')}®ion=${region}&stream=${streamId}`;
|
|
17
12
|
|
|
18
13
|
const { events, cancel } = client.sendSSERequest<T>(url);
|
|
19
14
|
|
package/src/test/client.test.ts
CHANGED
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
import { Logger } from 'winston';
|
|
2
2
|
import { LaplaceConfiguration } from '../utilities/configuration';
|
|
3
|
-
import {
|
|
3
|
+
import { createClient } from '../client/client';
|
|
4
4
|
import './client_test_suite';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
import { Region, Locale } from '../client/collections';
|
|
6
|
+
import { CollectionClient } from '../client/collections';
|
|
7
|
+
import { LaplaceHTTPError } from '../client/errors';
|
|
8
|
+
import { suite } from 'node:test';
|
|
9
9
|
|
|
10
10
|
class LaplaceClientTestSuite {
|
|
11
|
-
|
|
11
|
+
public config: LaplaceConfiguration;
|
|
12
12
|
|
|
13
13
|
constructor(config: LaplaceConfiguration) {
|
|
14
14
|
this.config = config;
|
|
15
15
|
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('LaplaceClient', () => {
|
|
19
|
+
let testSuite: LaplaceClientTestSuite;
|
|
20
|
+
|
|
21
|
+
beforeAll(() => {
|
|
22
|
+
// Assuming global.testSuite is set up as in the previous example
|
|
23
|
+
const config = (global as any).testSuite.config;
|
|
24
|
+
testSuite = new LaplaceClientTestSuite(config);
|
|
25
|
+
});
|
|
16
26
|
|
|
17
|
-
async
|
|
27
|
+
it('should make a successful request', async () => {
|
|
28
|
+
|
|
18
29
|
const logger: Logger = {
|
|
19
30
|
info: jest.fn(),
|
|
20
31
|
error: jest.fn(),
|
|
@@ -22,11 +33,11 @@ class LaplaceClientTestSuite {
|
|
|
22
33
|
debug: jest.fn(),
|
|
23
34
|
} as unknown as Logger;
|
|
24
35
|
|
|
25
|
-
const client = createClient(
|
|
36
|
+
const client = createClient(testSuite.config, logger);
|
|
26
37
|
|
|
27
38
|
const res = await client.sendRequest({
|
|
28
39
|
method: 'GET',
|
|
29
|
-
url: '/api/v1/
|
|
40
|
+
url: '/api/v1/sector',
|
|
30
41
|
params: {
|
|
31
42
|
region: Region.Tr,
|
|
32
43
|
locale: Locale.Tr,
|
|
@@ -35,19 +46,23 @@ class LaplaceClientTestSuite {
|
|
|
35
46
|
|
|
36
47
|
expect(res).toBeTruthy();
|
|
37
48
|
expect(res).not.toEqual({});
|
|
38
|
-
}
|
|
39
|
-
|
|
49
|
+
});
|
|
50
|
+
it('should fail if invalid token', async () => {
|
|
51
|
+
const logger: Logger = {
|
|
52
|
+
info: jest.fn(),
|
|
53
|
+
error: jest.fn(),
|
|
54
|
+
warn: jest.fn(),
|
|
55
|
+
debug: jest.fn(),
|
|
56
|
+
} as unknown as Logger;
|
|
40
57
|
|
|
41
|
-
|
|
42
|
-
|
|
58
|
+
var invalidConfig : LaplaceConfiguration = testSuite.config;
|
|
59
|
+
invalidConfig.apiKey = 'invalid';
|
|
43
60
|
|
|
44
|
-
|
|
45
|
-
// Assuming global.testSuite is set up as in the previous example
|
|
46
|
-
const config = (global as any).testSuite.config;
|
|
47
|
-
testSuite = new LaplaceClientTestSuite(config);
|
|
48
|
-
});
|
|
61
|
+
const client = new CollectionClient(invalidConfig, logger);
|
|
49
62
|
|
|
50
|
-
|
|
51
|
-
|
|
63
|
+
const f = async () => {await client.getAllIndustries(Region.Tr, Locale.Tr)};
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
await expect(f).rejects.toThrow(LaplaceHTTPError);
|
|
52
67
|
});
|
|
53
68
|
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Logger } from 'winston';
|
|
2
|
+
import { LaplaceConfiguration } from '../utilities/configuration';
|
|
3
|
+
import { CustomThemeClient, CollectionStatus, CreateCustomThemeParams, UpdateCustomThemeParams } from '../client/custom_theme';
|
|
4
|
+
import { Region, Locale, SortBy } from '../client/collections';
|
|
5
|
+
import { Stock, StockClient } from '../client/stocks';
|
|
6
|
+
import './client_test_suite';
|
|
7
|
+
|
|
8
|
+
describe('CustomTheme', () => {
|
|
9
|
+
let client: CustomThemeClient;
|
|
10
|
+
let stocksClient: StockClient;
|
|
11
|
+
|
|
12
|
+
beforeAll(() => {
|
|
13
|
+
const config = (global as any).testSuite.config as LaplaceConfiguration;
|
|
14
|
+
const logger: Logger = {
|
|
15
|
+
info: jest.fn(),
|
|
16
|
+
error: jest.fn(),
|
|
17
|
+
warn: jest.fn(),
|
|
18
|
+
debug: jest.fn(),
|
|
19
|
+
} as unknown as Logger;
|
|
20
|
+
|
|
21
|
+
client = new CustomThemeClient(config, logger);
|
|
22
|
+
stocksClient = new StockClient(config, logger);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('GetAllCustomThemes', async () => {
|
|
26
|
+
const resp = await client.getAllCustomThemes(Locale.Tr);
|
|
27
|
+
expect(resp).not.toBeEmpty();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('CreateUpdateDeleteCustomTheme', async () => {
|
|
31
|
+
const stocks = await stocksClient.getAllStocks(Region.Tr);
|
|
32
|
+
expect(stocks).not.toBeEmpty();
|
|
33
|
+
|
|
34
|
+
const createParams: CreateCustomThemeParams = {
|
|
35
|
+
title: {
|
|
36
|
+
[Locale.Tr]: 'Test Custom Theme',
|
|
37
|
+
},
|
|
38
|
+
description: {
|
|
39
|
+
[Locale.Tr]: 'Test Custom Theme Description',
|
|
40
|
+
},
|
|
41
|
+
region: [Region.Tr],
|
|
42
|
+
image_url: 'Test Custom Theme Image URL',
|
|
43
|
+
image: 'Test Custom Theme Image',
|
|
44
|
+
avatar_url: 'Test Custom Theme Avatar Image',
|
|
45
|
+
stocks: [stocks[0].id, stocks[1].id],
|
|
46
|
+
status: CollectionStatus.Active,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const id = await testCreateCustomTheme(client, createParams);
|
|
50
|
+
await testGetDetails(id, Locale.Tr, client, createParams);
|
|
51
|
+
|
|
52
|
+
let updateParams: UpdateCustomThemeParams = {
|
|
53
|
+
stockIds: [stocks[0].id, stocks[2].id],
|
|
54
|
+
};
|
|
55
|
+
await testUpdateCustomTheme(id, client, updateParams);
|
|
56
|
+
applyUpdateParams(updateParams, createParams);
|
|
57
|
+
await testGetDetails(id, Locale.Tr, client, createParams);
|
|
58
|
+
|
|
59
|
+
updateParams = {
|
|
60
|
+
title: {
|
|
61
|
+
[Locale.Tr]: 'Test Custom Theme Title Updated',
|
|
62
|
+
[Locale.En]: 'Test Custom Theme Title Updated',
|
|
63
|
+
},
|
|
64
|
+
description: {
|
|
65
|
+
[Locale.Tr]: 'Test Custom Theme Description Updated',
|
|
66
|
+
[Locale.En]: 'Test Custom Theme Description Updated',
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
await testUpdateCustomTheme(id, client, updateParams);
|
|
70
|
+
applyUpdateParams(updateParams, createParams);
|
|
71
|
+
await testGetDetails(id, Locale.Tr, client, createParams);
|
|
72
|
+
await testGetDetails(id, Locale.En, client, createParams);
|
|
73
|
+
|
|
74
|
+
updateParams = {
|
|
75
|
+
status: CollectionStatus.Inactive,
|
|
76
|
+
};
|
|
77
|
+
await testUpdateCustomTheme(id, client, updateParams);
|
|
78
|
+
applyUpdateParams(updateParams, createParams);
|
|
79
|
+
await testGetDetails(id, Locale.Tr, client, createParams);
|
|
80
|
+
|
|
81
|
+
await testDeleteCustomTheme(id, client);
|
|
82
|
+
await expect(client.getCustomThemeDetail(id, Locale.Tr, SortBy.PriceChange)).rejects.toThrow();
|
|
83
|
+
}, 15000);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
async function testCreateCustomTheme(client: CustomThemeClient, createParams: CreateCustomThemeParams): Promise<string> {
|
|
87
|
+
const resp = await client.createCustomTheme(createParams);
|
|
88
|
+
expect(resp).not.toBeEmpty();
|
|
89
|
+
return resp;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function testGetDetails(id: string, locale: Locale, client: CustomThemeClient, createParams: CreateCustomThemeParams) {
|
|
93
|
+
const resp = await client.getCustomThemeDetail(id, locale, null);
|
|
94
|
+
expect(resp).not.toBeEmpty();
|
|
95
|
+
|
|
96
|
+
expect(resp.title).toBe(createParams.title[locale]);
|
|
97
|
+
expect(resp.description).toBe(createParams.description?.[locale]);
|
|
98
|
+
expect(resp.region).toEqual(createParams.region);
|
|
99
|
+
expect(resp.imageUrl).toBe(createParams.image_url);
|
|
100
|
+
expect(resp.image).toBe(createParams.image);
|
|
101
|
+
expect(resp.avatarUrl).toBe(createParams.avatar_url);
|
|
102
|
+
expect(resp.stocks.map((stock: Stock) => stock.id)).toEqual(createParams.stocks);
|
|
103
|
+
expect(resp.status).toBe(createParams.status);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function testUpdateCustomTheme(id: string, client: CustomThemeClient, updateParams: UpdateCustomThemeParams) {
|
|
107
|
+
await client.updateCustomTheme(id, updateParams);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function applyUpdateParams(updateParams: UpdateCustomThemeParams, createParams: CreateCustomThemeParams) {
|
|
111
|
+
if (updateParams.stockIds) createParams.stocks = updateParams.stockIds;
|
|
112
|
+
if (updateParams.title) createParams.title = updateParams.title;
|
|
113
|
+
if (updateParams.description) createParams.description = updateParams.description;
|
|
114
|
+
if (updateParams.image_url) createParams.image_url = updateParams.image_url;
|
|
115
|
+
if (updateParams.image) createParams.image = updateParams.image;
|
|
116
|
+
if (updateParams.avatar_url) createParams.avatar_url = updateParams.avatar_url;
|
|
117
|
+
if (updateParams.status) createParams.status = updateParams.status;
|
|
118
|
+
if (updateParams.meta_data) createParams.meta_data = updateParams.meta_data;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function testDeleteCustomTheme(id: string, client: CustomThemeClient) {
|
|
122
|
+
await client.deleteCustomTheme(id);
|
|
123
|
+
}
|
|
@@ -19,8 +19,7 @@ describe('FinancialFundamentals', () => {
|
|
|
19
19
|
debug: jest.fn(),
|
|
20
20
|
} as unknown as Logger;
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
stockClient = new FinancialFundamentalsClient(client);
|
|
22
|
+
stockClient = new FinancialFundamentalsClient(config, logger);
|
|
24
23
|
});
|
|
25
24
|
|
|
26
25
|
test('GetStockDividends', async () => {
|
|
@@ -29,7 +28,7 @@ describe('FinancialFundamentals', () => {
|
|
|
29
28
|
});
|
|
30
29
|
|
|
31
30
|
test('GetStockStats', async () => {
|
|
32
|
-
|
|
31
|
+
var statKeys = [
|
|
33
32
|
StockStatsKey.PreviousClose,
|
|
34
33
|
StockStatsKey.YtdReturn,
|
|
35
34
|
StockStatsKey.YearlyReturn,
|
|
@@ -41,8 +40,22 @@ describe('FinancialFundamentals', () => {
|
|
|
41
40
|
StockStatsKey.ThreeYearReturn,
|
|
42
41
|
StockStatsKey.FiveYearReturn,
|
|
43
42
|
StockStatsKey.LatestPrice,
|
|
44
|
-
]
|
|
43
|
+
]
|
|
44
|
+
const resp = await stockClient.getStockStats(['TUPRS'], statKeys, Region.Tr);
|
|
45
45
|
expect(resp).not.toBeEmpty();
|
|
46
|
+
expect(resp.length).toBe(1);
|
|
47
|
+
expect(resp[0].symbol).toBe('TUPRS');
|
|
48
|
+
expect(resp[0].previousClose).toBeGreaterThan(0);
|
|
49
|
+
expect(resp[0].ytdReturn).not.toEqual(0);
|
|
50
|
+
expect(resp[0].yearlyReturn).not.toEqual(0);
|
|
51
|
+
expect(resp[0].marketCap).toBeGreaterThan(0);
|
|
52
|
+
expect(resp[0].peRatio).not.toEqual(0);
|
|
53
|
+
expect(resp[0].pbRatio).not.toEqual(0);
|
|
54
|
+
expect(resp[0].yearLow).toBeGreaterThan(0);
|
|
55
|
+
expect(resp[0].yearHigh).toBeGreaterThan(0);
|
|
56
|
+
// TODO fix these once the endpoint is fixed
|
|
57
|
+
// expect(resp[0]['3Year']).not.toEqual(0);
|
|
58
|
+
// expect(resp[0]['5Year']).not.toEqual(0);
|
|
46
59
|
});
|
|
47
60
|
|
|
48
61
|
test('GetTopMovers', async () => {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Logger } from 'winston';
|
|
2
|
+
import { LaplaceConfiguration } from '../utilities/configuration';
|
|
3
|
+
import { Client, createClient } from '../client/client';
|
|
4
|
+
import { FinancialClient, HistoricalRatiosKey, FinancialSheetType, FinancialSheetPeriod, Currency } from '../client/financial_ratios';
|
|
5
|
+
import { Region, Locale } from '../client/collections';
|
|
6
|
+
import './client_test_suite';
|
|
7
|
+
|
|
8
|
+
describe('FinancialRatios', () => {
|
|
9
|
+
let client: Client;
|
|
10
|
+
let financialClient: FinancialClient;
|
|
11
|
+
|
|
12
|
+
beforeAll(() => {
|
|
13
|
+
const config = (global as any).testSuite.config as LaplaceConfiguration;
|
|
14
|
+
const logger: Logger = {
|
|
15
|
+
info: jest.fn(),
|
|
16
|
+
error: jest.fn(),
|
|
17
|
+
warn: jest.fn(),
|
|
18
|
+
debug: jest.fn(),
|
|
19
|
+
} as unknown as Logger;
|
|
20
|
+
|
|
21
|
+
financialClient = new FinancialClient(config, logger);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('GetFinancialRatioComparison', async () => {
|
|
25
|
+
const resp = await financialClient.getFinancialRatioComparison('TUPRS', Region.Tr);
|
|
26
|
+
expect(resp).not.toBeEmpty();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('GetHistoricalRatios', async () => {
|
|
30
|
+
const resp = await financialClient.getHistoricalRatios('TUPRS', [HistoricalRatiosKey.PriceToEarningsRatio], Region.Tr);
|
|
31
|
+
expect(resp).not.toBeEmpty();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('GetHistoricalRatiosDescriptions', async () => {
|
|
35
|
+
const resp = await financialClient.getHistoricalRatiosDescriptions(Locale.Tr, Region.Tr);
|
|
36
|
+
expect(resp).not.toBeEmpty();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('GetHistoricalFinancialSheets', async () => {
|
|
40
|
+
const resp = await financialClient.getHistoricalFinancialSheets(
|
|
41
|
+
'TUPRS',
|
|
42
|
+
{ year: 2022, month: 1, day: 1 },
|
|
43
|
+
{ year: 2023, month: 1, day: 1 },
|
|
44
|
+
FinancialSheetType.BalanceSheet,
|
|
45
|
+
FinancialSheetPeriod.Annual,
|
|
46
|
+
Currency.EUR,
|
|
47
|
+
Region.Tr
|
|
48
|
+
);
|
|
49
|
+
expect(resp).not.toBeEmpty();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// import { Logger } from 'winston';
|
|
2
|
+
// import { LaplaceConfiguration } from '../utilities/configuration';
|
|
3
|
+
// import { Client, createClient } from '../client/client';
|
|
4
|
+
// import { LivePriceClient, BISTStockLiveData, USStockLiveData } from '../client/live_price';
|
|
5
|
+
// import { Region } from '../client/collections';
|
|
6
|
+
// import './client_test_suite';
|
|
7
|
+
|
|
8
|
+
// describe('LivePrice', () => {
|
|
9
|
+
// let client: Client;
|
|
10
|
+
// let livePriceClient: LivePriceClient;
|
|
11
|
+
|
|
12
|
+
// beforeAll(() => {
|
|
13
|
+
// const config = (global as any).testSuite.config as LaplaceConfiguration;
|
|
14
|
+
// const logger: Logger = {
|
|
15
|
+
// info: jest.fn(),
|
|
16
|
+
// error: jest.fn(),
|
|
17
|
+
// warn: jest.fn(),
|
|
18
|
+
// debug: jest.fn(),
|
|
19
|
+
// } as unknown as Logger;
|
|
20
|
+
|
|
21
|
+
// client = createClient(config, logger);
|
|
22
|
+
// livePriceClient = new LivePriceClient(client);
|
|
23
|
+
// });
|
|
24
|
+
|
|
25
|
+
// const testLivePrice = async (
|
|
26
|
+
// symbols: string[],
|
|
27
|
+
// region: Region,
|
|
28
|
+
// getLivePriceFunc: (symbols: string[], region: Region) => AsyncGenerator<BISTStockLiveData | USStockLiveData, void, unknown>
|
|
29
|
+
// ) => {
|
|
30
|
+
// const livePriceGenerator = getLivePriceFunc.call(livePriceClient, symbols, region);
|
|
31
|
+
// let livePriceCount = 0;
|
|
32
|
+
|
|
33
|
+
// try {
|
|
34
|
+
// for await (const livePrice of livePriceGenerator) {
|
|
35
|
+
// expect(livePrice).not.toBeEmpty();
|
|
36
|
+
// livePriceCount++;
|
|
37
|
+
// if (livePriceCount > 3) {
|
|
38
|
+
// break;
|
|
39
|
+
// }
|
|
40
|
+
// }
|
|
41
|
+
// } catch (error) {
|
|
42
|
+
// throw new Error(`Error occurred during live price retrieval: ${error}`);
|
|
43
|
+
// }
|
|
44
|
+
|
|
45
|
+
// expect(livePriceCount).toBeGreaterThan(0);
|
|
46
|
+
// };
|
|
47
|
+
|
|
48
|
+
// test('BISTLivePrice', async () => {
|
|
49
|
+
// const symbols = ['TUPRS', 'SASA', 'THYAO', 'GARAN', 'YKBN'];
|
|
50
|
+
// await testLivePrice(symbols, Region.Tr, livePriceClient.getLivePriceForBIST);
|
|
51
|
+
// }, 10000);
|
|
52
|
+
|
|
53
|
+
// test('USLivePrice', async () => {
|
|
54
|
+
// const symbols = ['AAPL', 'GOOGL', 'MSFT', 'AMZN', 'META'];
|
|
55
|
+
// await testLivePrice(symbols, Region.Us, livePriceClient.getLivePriceForUS);
|
|
56
|
+
// }, 10000);
|
|
57
|
+
// });
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { Logger } from 'winston';
|
|
2
|
-
import { LaplaceConfiguration } from '../utilities/configuration';
|
|
3
|
-
import { Client, createClient } from '../client/client';
|
|
4
|
-
import { LivePriceClient, Region, BISTStockLiveData, USStockLiveData } from '../client/live_price';
|
|
5
|
-
import './client_test_suite';
|
|
6
|
-
|
|
7
|
-
describe('LivePrice', () => {
|
|
8
|
-
let client: Client;
|
|
9
|
-
let livePriceClient: LivePriceClient;
|
|
10
|
-
|
|
11
|
-
beforeAll(() => {
|
|
12
|
-
const config = (global as any).testSuite.config as LaplaceConfiguration;
|
|
13
|
-
const logger: Logger = {
|
|
14
|
-
info: jest.fn(),
|
|
15
|
-
error: jest.fn(),
|
|
16
|
-
warn: jest.fn(),
|
|
17
|
-
debug: jest.fn(),
|
|
18
|
-
} as unknown as Logger;
|
|
19
|
-
|
|
20
|
-
client = createClient(config, logger);
|
|
21
|
-
livePriceClient = new LivePriceClient(client);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const testLivePrice = async (
|
|
25
|
-
symbols: string[],
|
|
26
|
-
region: Region,
|
|
27
|
-
getLivePriceFunc: (symbols: string[], region: Region) => AsyncGenerator<BISTStockLiveData | USStockLiveData, void, unknown>
|
|
28
|
-
) => {
|
|
29
|
-
const livePriceGenerator = getLivePriceFunc.call(livePriceClient, symbols, region);
|
|
30
|
-
let livePriceCount = 0;
|
|
31
|
-
|
|
32
|
-
jest.setTimeout(10000); // 10 seconds timeout
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
for await (const livePrice of livePriceGenerator) {
|
|
36
|
-
expect(livePrice).not.toBeEmpty();
|
|
37
|
-
livePriceCount++;
|
|
38
|
-
if (livePriceCount > 3) {
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
} catch (error) {
|
|
43
|
-
throw new Error(`Error occurred during live price retrieval: ${error}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
expect(livePriceCount).toBeGreaterThan(0);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
test('BISTLivePrice', async () => {
|
|
50
|
-
const symbols = ['TUPRS', 'SASA', 'THYAO', 'GARAN', 'YKBN'];
|
|
51
|
-
await testLivePrice(symbols, Region.BIST, livePriceClient.getLivePriceForBIST);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('USLivePrice', async () => {
|
|
55
|
-
const symbols = ['AAPL', 'GOOGL', 'MSFT', 'AMZN', 'META'];
|
|
56
|
-
await testLivePrice(symbols, Region.US, livePriceClient.getLivePriceForUS);
|
|
57
|
-
});
|
|
58
|
-
});
|