borsajs 0.1.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.
Files changed (109) hide show
  1. package/LICENSE +201 -0
  2. package/README.en.md +209 -0
  3. package/README.md +57 -0
  4. package/dist/cache.d.ts +24 -0
  5. package/dist/cache.d.ts.map +1 -0
  6. package/dist/cache.js +40 -0
  7. package/dist/cache.js.map +1 -0
  8. package/dist/crypto.d.ts +17 -0
  9. package/dist/crypto.d.ts.map +1 -0
  10. package/dist/crypto.js +25 -0
  11. package/dist/crypto.js.map +1 -0
  12. package/dist/exceptions.d.ts +34 -0
  13. package/dist/exceptions.d.ts.map +1 -0
  14. package/dist/exceptions.js +70 -0
  15. package/dist/exceptions.js.map +1 -0
  16. package/dist/fund.d.ts +28 -0
  17. package/dist/fund.d.ts.map +1 -0
  18. package/dist/fund.js +29 -0
  19. package/dist/fund.js.map +1 -0
  20. package/dist/fx.d.ts +16 -0
  21. package/dist/fx.d.ts.map +1 -0
  22. package/dist/fx.js +22 -0
  23. package/dist/fx.js.map +1 -0
  24. package/dist/index-class.d.ts +21 -0
  25. package/dist/index-class.d.ts.map +1 -0
  26. package/dist/index-class.js +31 -0
  27. package/dist/index-class.js.map +1 -0
  28. package/dist/index.d.ts +26 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +16 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/inflation.d.ts +12 -0
  33. package/dist/inflation.d.ts.map +1 -0
  34. package/dist/inflation.js +28 -0
  35. package/dist/inflation.js.map +1 -0
  36. package/dist/market.d.ts +7 -0
  37. package/dist/market.d.ts.map +1 -0
  38. package/dist/market.js +7 -0
  39. package/dist/market.js.map +1 -0
  40. package/dist/multi.d.ts +26 -0
  41. package/dist/multi.d.ts.map +1 -0
  42. package/dist/multi.js +46 -0
  43. package/dist/multi.js.map +1 -0
  44. package/dist/providers/base.d.ts +24 -0
  45. package/dist/providers/base.d.ts.map +1 -0
  46. package/dist/providers/base.js +28 -0
  47. package/dist/providers/base.js.map +1 -0
  48. package/dist/providers/btcturk.d.ts +43 -0
  49. package/dist/providers/btcturk.d.ts.map +1 -0
  50. package/dist/providers/btcturk.js +94 -0
  51. package/dist/providers/btcturk.js.map +1 -0
  52. package/dist/providers/dovizcom.d.ts +42 -0
  53. package/dist/providers/dovizcom.d.ts.map +1 -0
  54. package/dist/providers/dovizcom.js +105 -0
  55. package/dist/providers/dovizcom.js.map +1 -0
  56. package/dist/providers/index.d.ts +20 -0
  57. package/dist/providers/index.d.ts.map +1 -0
  58. package/dist/providers/index.js +12 -0
  59. package/dist/providers/index.js.map +1 -0
  60. package/dist/providers/kap.d.ts +37 -0
  61. package/dist/providers/kap.d.ts.map +1 -0
  62. package/dist/providers/kap.js +110 -0
  63. package/dist/providers/kap.js.map +1 -0
  64. package/dist/providers/paratic.d.ts +41 -0
  65. package/dist/providers/paratic.d.ts.map +1 -0
  66. package/dist/providers/paratic.js +85 -0
  67. package/dist/providers/paratic.js.map +1 -0
  68. package/dist/providers/tcmb.d.ts +49 -0
  69. package/dist/providers/tcmb.d.ts.map +1 -0
  70. package/dist/providers/tcmb.js +99 -0
  71. package/dist/providers/tcmb.js.map +1 -0
  72. package/dist/providers/tefas.d.ts +51 -0
  73. package/dist/providers/tefas.d.ts.map +1 -0
  74. package/dist/providers/tefas.js +89 -0
  75. package/dist/providers/tefas.js.map +1 -0
  76. package/dist/providers/viop.d.ts +35 -0
  77. package/dist/providers/viop.d.ts.map +1 -0
  78. package/dist/providers/viop.js +140 -0
  79. package/dist/providers/viop.js.map +1 -0
  80. package/dist/ticker.d.ts +18 -0
  81. package/dist/ticker.d.ts.map +1 -0
  82. package/dist/ticker.js +23 -0
  83. package/dist/ticker.js.map +1 -0
  84. package/dist/viop.d.ts +19 -0
  85. package/dist/viop.d.ts.map +1 -0
  86. package/dist/viop.js +31 -0
  87. package/dist/viop.js.map +1 -0
  88. package/package.json +52 -0
  89. package/src/cache.ts +42 -0
  90. package/src/crypto.ts +30 -0
  91. package/src/exceptions.ts +77 -0
  92. package/src/fund.ts +37 -0
  93. package/src/fx.ts +26 -0
  94. package/src/index-class.ts +39 -0
  95. package/src/index.ts +38 -0
  96. package/src/inflation.ts +31 -0
  97. package/src/market.ts +7 -0
  98. package/src/multi.ts +46 -0
  99. package/src/providers/base.ts +35 -0
  100. package/src/providers/btcturk.ts +100 -0
  101. package/src/providers/dovizcom.ts +98 -0
  102. package/src/providers/kap.ts +98 -0
  103. package/src/providers/paratic.ts +95 -0
  104. package/src/providers/tcmb.ts +96 -0
  105. package/src/providers/tefas.ts +85 -0
  106. package/src/ticker.ts +30 -0
  107. package/src/viop.ts +35 -0
  108. package/test/demo.ts +99 -0
  109. package/tsconfig.json +34 -0
@@ -0,0 +1,98 @@
1
+ /**
2
+ * doviz.com provider for forex and commodity data.
3
+ */
4
+ import { BaseProvider, ProviderOptions } from './base.js';
5
+ import { TTL } from '../cache.js';
6
+ import { APIError, DataNotAvailableError } from '../exceptions.js';
7
+
8
+ export interface FXCurrentData { symbol: string; last: number; open: number; high: number; low: number; updateTime?: Date; }
9
+ export interface FXHistoryData { date: Date; open: number; high: number; low: number; close: number; }
10
+ export interface HistoryOptions { period?: string; start?: Date; end?: Date; }
11
+ interface ArchiveItem { update_date?: number; open?: number; highest?: number; lowest?: number; close?: number; }
12
+ interface DovizApiResponse { data?: { archive?: ArchiveItem[]; }; }
13
+
14
+ export class DovizcomProvider extends BaseProvider {
15
+ private static readonly BASE_URL = 'https://api.doviz.com/api/v12';
16
+ private static readonly FALLBACK_TOKEN = '3e75d7fabf1c50c8b962626dd0e5ea22d8000815e1b0920d0a26afd77fcd6609';
17
+ private static readonly PERIOD_DAYS: Record<string, number> = { '1d': 1, '5d': 5, '1mo': 30, '3mo': 90, '6mo': 180, '1y': 365 };
18
+ private static readonly SUPPORTED_ASSETS = new Set(['USD', 'EUR', 'GBP', 'JPY', 'CHF', 'CAD', 'AUD', 'gram-altin', 'gumus', 'ons', 'XAG-USD', 'XPT-USD', 'XPD-USD', 'BRENT', 'WTI', 'diesel', 'gasoline', 'lpg']);
19
+ private static readonly FUEL_ASSETS = new Set(['gasoline', 'diesel', 'lpg']);
20
+ private token: string | null = null; private tokenExpiry: number = 0;
21
+
22
+ constructor(options?: ProviderOptions) { super(options); }
23
+
24
+ private async getToken(): Promise<string> {
25
+ if (this.token && Date.now() < this.tokenExpiry) return this.token;
26
+ try {
27
+ const response = await this.get<string>('https://www.doviz.com/', { responseType: 'text' as const });
28
+ const match = response.data.match(/token["']?\s*:\s*["']([a-f0-9]{64})["']/i) || response.data.match(/Bearer\s+([a-f0-9]{64})/i);
29
+ if (match) { this.token = match[1]; this.tokenExpiry = Date.now() + 3600000; return this.token; }
30
+ } catch { /* fallback */ }
31
+ return DovizcomProvider.FALLBACK_TOKEN;
32
+ }
33
+
34
+ private getOrigin(asset: string): string { return ['gram-altin', 'gumus', 'ons'].includes(asset) ? 'https://altin.doviz.com' : 'https://www.doviz.com'; }
35
+
36
+ async getCurrent(asset: string): Promise<FXCurrentData> {
37
+ const normalizedAsset = DovizcomProvider.SUPPORTED_ASSETS.has(asset.toUpperCase()) ? asset.toUpperCase() : asset;
38
+ if (!DovizcomProvider.SUPPORTED_ASSETS.has(normalizedAsset)) throw new DataNotAvailableError(`Unsupported asset: ${asset}`);
39
+
40
+ const cacheKey = `dovizcom:current:${normalizedAsset}`;
41
+ const cached = this.cacheGet<FXCurrentData>(cacheKey);
42
+ if (cached) return cached;
43
+
44
+ try {
45
+ const data = DovizcomProvider.FUEL_ASSETS.has(normalizedAsset) ? await this.getFromArchive(normalizedAsset, 7) : await this.getFromDaily(normalizedAsset);
46
+ if (!data) throw new DataNotAvailableError(`No data for ${asset}`);
47
+ const result: FXCurrentData = { symbol: normalizedAsset, last: Number(data.close) || 0, open: Number(data.open) || 0, high: Number(data.highest) || 0, low: Number(data.lowest) || 0, updateTime: data.update_date ? new Date(data.update_date * 1000) : undefined };
48
+ this.cacheSet(cacheKey, result, TTL.FX_RATES);
49
+ return result;
50
+ } catch (error) {
51
+ if (error instanceof DataNotAvailableError) throw error;
52
+ throw new APIError(`Failed to fetch current for ${asset}: ${error}`);
53
+ }
54
+ }
55
+
56
+ async getHistory(asset: string, options: HistoryOptions = {}): Promise<FXHistoryData[]> {
57
+ const { period = '1mo', start, end } = options;
58
+ const normalizedAsset = DovizcomProvider.SUPPORTED_ASSETS.has(asset.toUpperCase()) ? asset.toUpperCase() : asset;
59
+ if (!DovizcomProvider.SUPPORTED_ASSETS.has(normalizedAsset)) throw new DataNotAvailableError(`Unsupported asset: ${asset}`);
60
+
61
+ const endDt = end ?? new Date();
62
+ const startDt = start ?? new Date(endDt.getTime() - (DovizcomProvider.PERIOD_DAYS[period] ?? 30) * 24 * 60 * 60 * 1000);
63
+ const cacheKey = `dovizcom:history:${normalizedAsset}:${startDt.toISOString()}:${endDt.toISOString()}`;
64
+ const cached = this.cacheGet<FXHistoryData[]>(cacheKey);
65
+ if (cached) return cached;
66
+
67
+ try {
68
+ const token = await this.getToken();
69
+ const origin = this.getOrigin(normalizedAsset);
70
+ const response = await this.get<DovizApiResponse>(`${DovizcomProvider.BASE_URL}/assets/${normalizedAsset}/archive`, {
71
+ params: { start: Math.floor(startDt.getTime() / 1000), end: Math.floor(endDt.getTime() / 1000) },
72
+ headers: { 'Authorization': `Bearer ${token}`, 'Origin': origin, 'Referer': `${origin}/` },
73
+ });
74
+ const records: FXHistoryData[] = (response.data?.data?.archive ?? []).map(item => ({
75
+ date: new Date((item.update_date ?? 0) * 1000), open: Number(item.open) || 0, high: Number(item.highest) || 0, low: Number(item.lowest) || 0, close: Number(item.close) || 0,
76
+ }));
77
+ records.sort((a, b) => a.date.getTime() - b.date.getTime());
78
+ this.cacheSet(cacheKey, records, TTL.OHLCV_HISTORY);
79
+ return records;
80
+ } catch (error) { if (error instanceof DataNotAvailableError) throw error; throw new APIError(`Failed to fetch history for ${asset}: ${error}`); }
81
+ }
82
+
83
+ private async getFromDaily(asset: string): Promise<ArchiveItem | null> {
84
+ const token = await this.getToken(); const origin = this.getOrigin(asset);
85
+ const response = await this.get<DovizApiResponse>(`${DovizcomProvider.BASE_URL}/assets/${asset}/daily`, { params: { limit: 1 }, headers: { 'Authorization': `Bearer ${token}`, 'Origin': origin, 'Referer': `${origin}/` } });
86
+ const archive = response.data?.data?.archive ?? []; return archive.length > 0 ? archive[0] : null;
87
+ }
88
+
89
+ private async getFromArchive(asset: string, days: number): Promise<ArchiveItem | null> {
90
+ const token = await this.getToken(); const origin = this.getOrigin(asset);
91
+ const endTime = Math.floor(Date.now() / 1000); const startTime = endTime - (days * 86400);
92
+ const response = await this.get<DovizApiResponse>(`${DovizcomProvider.BASE_URL}/assets/${asset}/archive`, { params: { start: startTime, end: endTime }, headers: { 'Authorization': `Bearer ${token}`, 'Origin': origin, 'Referer': `${origin}/` } });
93
+ const archive = response.data?.data?.archive ?? []; return archive.length > 0 ? archive[archive.length - 1] : null;
94
+ }
95
+ }
96
+
97
+ let provider: DovizcomProvider | null = null;
98
+ export function getDovizcomProvider(): DovizcomProvider { if (!provider) provider = new DovizcomProvider(); return provider; }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * KAP provider for company data and VIOP provider.
3
+ */
4
+ import * as cheerio from 'cheerio';
5
+ import { BaseProvider, ProviderOptions } from './base.js';
6
+ import { TTL } from '../cache.js';
7
+ import { APIError } from '../exceptions.js';
8
+
9
+ export interface Company { ticker: string; name: string; city: string; }
10
+ export interface Disclosure { id: string; date: Date; title: string; type: string; url: string; }
11
+
12
+ export class KapProvider extends BaseProvider {
13
+ private static readonly BASE_URL = 'https://www.kap.org.tr';
14
+ constructor(options?: ProviderOptions) { super(options); }
15
+
16
+ async getCompanies(): Promise<Company[]> {
17
+ const cacheKey = 'kap:companies';
18
+ const cached = this.cacheGet<Company[]>(cacheKey);
19
+ if (cached) return cached;
20
+ try {
21
+ const response = await this.get<string>(`${KapProvider.BASE_URL}/tr/bist-sirketleri`, { responseType: 'text' as const });
22
+ const $ = cheerio.load(response.data);
23
+ const companies: Company[] = [];
24
+ $('table.dataTable tbody tr').each((_, row) => {
25
+ const cells = $(row).find('td');
26
+ if (cells.length >= 3) companies.push({ ticker: $(cells[0]).text().trim(), name: $(cells[1]).text().trim(), city: $(cells[2]).text().trim() });
27
+ });
28
+ this.cacheSet(cacheKey, companies, TTL.COMPANY_LIST);
29
+ return companies;
30
+ } catch (error) { throw new APIError(`Failed to fetch companies: ${error}`); }
31
+ }
32
+
33
+ async search(query: string): Promise<Company[]> {
34
+ const companies = await this.getCompanies();
35
+ const q = query.toLowerCase();
36
+ return companies.filter(c => c.ticker.toLowerCase().includes(q) || c.name.toLowerCase().includes(q));
37
+ }
38
+ }
39
+
40
+ export interface ContractData { code: string; contract: string; price: number; change: number; volumeTl: number; volumeQty: number; category: string; }
41
+
42
+ export class ViopProvider extends BaseProvider {
43
+ private static readonly BASE_URL = 'https://www.isyatirim.com.tr/tr-tr/analiz/Sayfalar/viop-akis.aspx';
44
+ constructor(options?: ProviderOptions) { super(options); }
45
+
46
+ async getFutures(category: 'all' | 'stock' | 'index' | 'currency' | 'commodity' = 'all'): Promise<ContractData[]> {
47
+ const cacheKey = `viop:futures:${category}`;
48
+ const cached = this.cacheGet<ContractData[]>(cacheKey);
49
+ if (cached) return cached;
50
+ try {
51
+ const response = await this.get<string>(ViopProvider.BASE_URL, { responseType: 'text' as const });
52
+ const $ = cheerio.load(response.data);
53
+ const contracts: ContractData[] = [];
54
+ $('table.dataTable tbody tr').each((_, row) => {
55
+ const cells = $(row).find('td');
56
+ if (cells.length < 6) return;
57
+ const code = $(cells[0]).text().trim();
58
+ let cat = 'stock';
59
+ if (code.includes('XU') || code.includes('XLBNK')) cat = 'index';
60
+ else if (code.includes('USD') || code.includes('EUR')) cat = 'currency';
61
+ else if (code.includes('GAU') || code.includes('XAU')) cat = 'commodity';
62
+ if (category !== 'all' && cat !== category) return;
63
+ const parseNum = (t: string) => parseFloat(t.replace(/\./g, '').replace(',', '.').replace(/[^\d.-]/g, '')) || 0;
64
+ contracts.push({ code, contract: $(cells[1]).text().trim(), price: parseNum($(cells[2]).text()), change: parseNum($(cells[3]).text()), volumeTl: parseNum($(cells[4]).text()), volumeQty: parseNum($(cells[5]).text()), category: cat });
65
+ });
66
+ this.cacheSet(cacheKey, contracts, TTL.VIOP);
67
+ return contracts;
68
+ } catch (error) { throw new APIError(`Failed to fetch futures: ${error}`); }
69
+ }
70
+
71
+ async getOptions(category: 'all' | 'stock' | 'index' = 'all'): Promise<ContractData[]> {
72
+ const cacheKey = `viop:options:${category}`;
73
+ const cached = this.cacheGet<ContractData[]>(cacheKey);
74
+ if (cached) return cached;
75
+ try {
76
+ const response = await this.get<string>(ViopProvider.BASE_URL, { responseType: 'text' as const });
77
+ const $ = cheerio.load(response.data);
78
+ const contracts: ContractData[] = [];
79
+ $('table.optionsTable tbody tr').each((_, row) => {
80
+ const cells = $(row).find('td');
81
+ if (cells.length < 6) return;
82
+ const code = $(cells[0]).text().trim();
83
+ let cat = 'stock';
84
+ if (code.includes('XU') || code.includes('XLBNK')) cat = 'index';
85
+ if (category !== 'all' && cat !== category) return;
86
+ const parseNum = (t: string) => parseFloat(t.replace(/\./g, '').replace(',', '.').replace(/[^\d.-]/g, '')) || 0;
87
+ contracts.push({ code, contract: $(cells[1]).text().trim(), price: parseNum($(cells[2]).text()), change: parseNum($(cells[3]).text()), volumeTl: parseNum($(cells[4]).text()), volumeQty: parseNum($(cells[5]).text()), category: cat });
88
+ });
89
+ this.cacheSet(cacheKey, contracts, TTL.VIOP);
90
+ return contracts;
91
+ } catch (error) { throw new APIError(`Failed to fetch options: ${error}`); }
92
+ }
93
+ }
94
+
95
+ let kapProvider: KapProvider | null = null;
96
+ export function getKapProvider(): KapProvider { if (!kapProvider) kapProvider = new KapProvider(); return kapProvider; }
97
+ let viopProvider: ViopProvider | null = null;
98
+ export function getViopProvider(): ViopProvider { if (!viopProvider) viopProvider = new ViopProvider(); return viopProvider; }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Paratic provider for historical OHLCV data.
3
+ */
4
+ import { BaseProvider, ProviderOptions } from './base.js';
5
+ import { TTL } from '../cache.js';
6
+ import { APIError, DataNotAvailableError, TickerNotFoundError } from '../exceptions.js';
7
+
8
+ export interface QuoteData {
9
+ symbol: string; last: number; open: number; high: number; low: number; close: number;
10
+ volume: number; change: number; changePercent: number; updateTime: Date;
11
+ }
12
+
13
+ export interface OHLCVData { date: Date; open: number; high: number; low: number; close: number; volume: number; }
14
+ export interface HistoryOptions { period?: string; interval?: string; start?: Date; end?: Date; }
15
+ interface ParaticDataItem { d: number; o: number; h: number; l: number; c: number; v: number; }
16
+
17
+ export class ParaticProvider extends BaseProvider {
18
+ private static readonly BASE_URL = 'https://piyasa.paratic.com/API/g.php';
19
+ private static readonly PERIOD_MAP: Record<string, number> = { '1d': 1, '5d': 5, '1mo': 30, '3mo': 90, '6mo': 180, '1y': 365, '2y': 730, '5y': 1825, '10y': 3650, 'max': 3650 };
20
+ private static readonly INTERVAL_MAP: Record<string, number> = { '1m': 1, '3m': 3, '5m': 5, '15m': 15, '30m': 30, '45m': 45, '1h': 60, '1d': 1440, '1wk': 10080, '1mo': 43200 };
21
+
22
+ constructor(options?: ProviderOptions) { super(options); }
23
+
24
+ private formatDate(date: Date): string {
25
+ const y = date.getFullYear(), m = String(date.getMonth() + 1).padStart(2, '0'), d = String(date.getDate()).padStart(2, '0');
26
+ const h = String(date.getHours()).padStart(2, '0'), mi = String(date.getMinutes()).padStart(2, '0'), s = String(date.getSeconds()).padStart(2, '0');
27
+ return `${y}${m}${d}${h}${mi}${s}`;
28
+ }
29
+
30
+ async getQuote(symbol: string): Promise<QuoteData> {
31
+ symbol = symbol.toUpperCase().replace('.IS', '').replace('.E', '');
32
+ const cacheKey = `paratic:quote:${symbol}`;
33
+ const cached = this.cacheGet<QuoteData>(cacheKey);
34
+ if (cached) return cached;
35
+
36
+ const params = { a: 'd', c: symbol, p: 1440, from: '', at: this.formatDate(new Date()), group: 'f' };
37
+ try {
38
+ const response = await this.get<ParaticDataItem[]>(ParaticProvider.BASE_URL, { params });
39
+ const data = response.data;
40
+ if (!data || data.length === 0) throw new TickerNotFoundError(symbol);
41
+
42
+ const latest = data[data.length - 1];
43
+ const prev = data.length > 1 ? data[data.length - 2] : null;
44
+ const prevClose = prev ? Number(prev.c) || 0 : 0;
45
+ const last = Number(latest.c) || 0;
46
+ const change = prevClose ? last - prevClose : 0;
47
+ const changePct = prevClose ? (change / prevClose) * 100 : 0;
48
+
49
+ const result: QuoteData = {
50
+ symbol, last, open: Number(latest.o) || 0, high: Number(latest.h) || 0, low: Number(latest.l) || 0,
51
+ close: prevClose, volume: Number(latest.v) || 0, change: Math.round(change * 100) / 100,
52
+ changePercent: Math.round(changePct * 100) / 100, updateTime: new Date(latest.d),
53
+ };
54
+ this.cacheSet(cacheKey, result, TTL.REALTIME_PRICE);
55
+ return result;
56
+ } catch (error) {
57
+ if (error instanceof TickerNotFoundError) throw error;
58
+ throw new APIError(`Failed to fetch quote for ${symbol}: ${error}`);
59
+ }
60
+ }
61
+
62
+ async getHistory(symbol: string, options: HistoryOptions = {}): Promise<OHLCVData[]> {
63
+ symbol = symbol.toUpperCase().replace('.IS', '').replace('.E', '');
64
+ const { period = '1mo', interval = '1d', start, end } = options;
65
+ const endDt = end ?? new Date();
66
+ const startDt = start ?? new Date(endDt.getTime() - (ParaticProvider.PERIOD_MAP[period] ?? 30) * 24 * 60 * 60 * 1000);
67
+
68
+ let cacheKey = `paratic:history:${symbol}:${period}:${interval}`;
69
+ if (start) cacheKey += `:${start.toISOString()}`;
70
+ if (end) cacheKey += `:${end.toISOString()}`;
71
+ const cached = this.cacheGet<OHLCVData[]>(cacheKey);
72
+ if (cached) return cached;
73
+
74
+ const intervalMinutes = ParaticProvider.INTERVAL_MAP[interval] ?? 1440;
75
+ const params = { a: 'd', c: symbol, p: intervalMinutes, from: '', at: this.formatDate(endDt), group: 'f' };
76
+
77
+ try {
78
+ const response = await this.get<ParaticDataItem[]>(ParaticProvider.BASE_URL, { params });
79
+ const data = response.data;
80
+ if (!data || data.length === 0) throw new DataNotAvailableError(`No data available for ${symbol}`);
81
+
82
+ const records: OHLCVData[] = data.filter(item => item.d && new Date(item.d) >= startDt && new Date(item.d) <= endDt)
83
+ .map(item => ({ date: new Date(item.d), open: Number(item.o) || 0, high: Number(item.h) || 0, low: Number(item.l) || 0, close: Number(item.c) || 0, volume: Number(item.v) || 0 }));
84
+ records.sort((a, b) => a.date.getTime() - b.date.getTime());
85
+ this.cacheSet(cacheKey, records, TTL.OHLCV_HISTORY);
86
+ return records;
87
+ } catch (error) {
88
+ if (error instanceof DataNotAvailableError) throw error;
89
+ throw new APIError(`Failed to fetch data for ${symbol}: ${error}`);
90
+ }
91
+ }
92
+ }
93
+
94
+ let provider: ParaticProvider | null = null;
95
+ export function getParaticProvider(): ParaticProvider { if (!provider) provider = new ParaticProvider(); return provider; }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * TCMB provider for Turkish inflation data.
3
+ */
4
+ import * as cheerio from 'cheerio';
5
+ import { BaseProvider, ProviderOptions } from './base.js';
6
+ import { TTL } from '../cache.js';
7
+ import { APIError, DataNotAvailableError } from '../exceptions.js';
8
+
9
+ export interface InflationLatest { date: string; yearMonth: string; yearlyInflation: number; monthlyInflation: number; type: string; }
10
+ export interface InflationData { date: Date; yearMonth: string; yearlyInflation: number; monthlyInflation: number; }
11
+ export interface InflationCalculation { startDate: string; endDate: string; initialValue: number; finalValue: number; totalYears: number; totalMonths: number; totalChange: number; avgYearlyInflation: number; startCpi: number; endCpi: number; }
12
+ export interface InflationDataOptions { start?: string; end?: string; limit?: number; }
13
+
14
+ interface CalcApiResponse { yeniSepetDeger?: string; toplamYil?: number; toplamAy?: number; toplamDegisim?: string; ortalamaYillikEnflasyon?: string; ilkYilTufe?: string; sonYilTufe?: string; }
15
+
16
+ export class TcmbProvider extends BaseProvider {
17
+ private static readonly BASE_URL = 'https://www.tcmb.gov.tr';
18
+ private static readonly CALC_API_URL = 'https://appg.tcmb.gov.tr/KIMENFH/enflasyon/hesapla';
19
+ private static readonly INFLATION_PATHS: Record<string, string> = { tufe: '/wps/wcm/connect/tr/tcmb+tr/main+menu/istatistikler/enflasyon+verileri', ufe: '/wps/wcm/connect/TR/TCMB+TR/Main+Menu/Istatistikler/Enflasyon+Verileri/Uretici+Fiyatlari' };
20
+
21
+ constructor(options?: ProviderOptions) { super(options); }
22
+
23
+ async getLatest(inflationType: string = 'tufe'): Promise<InflationLatest> {
24
+ const data = await this.getData(inflationType, { limit: 1 });
25
+ if (data.length === 0) throw new DataNotAvailableError('No inflation data available');
26
+ const latest = data[0];
27
+ return { date: latest.date.toISOString().split('T')[0], yearMonth: latest.yearMonth, yearlyInflation: latest.yearlyInflation, monthlyInflation: latest.monthlyInflation, type: inflationType.toUpperCase() };
28
+ }
29
+
30
+ async getData(inflationType: string, options: InflationDataOptions = {}): Promise<InflationData[]> {
31
+ const { start, end, limit } = options;
32
+ const type = inflationType.toLowerCase();
33
+ if (!TcmbProvider.INFLATION_PATHS[type]) throw new Error(`Invalid type: ${type}. Use 'tufe' or 'ufe'`);
34
+
35
+ const cacheKey = `tcmb:data:${type}`;
36
+ let cached = this.cacheGet<InflationData[]>(cacheKey);
37
+
38
+ if (!cached) {
39
+ try {
40
+ const response = await this.get<string>(TcmbProvider.BASE_URL + TcmbProvider.INFLATION_PATHS[type], { responseType: 'text' as const, headers: { 'Accept': 'text/html', 'Accept-Language': 'tr-TR,tr;q=0.9' } });
41
+ cached = this.parseInflationTable(response.data);
42
+ this.cacheSet(cacheKey, cached, TTL.FX_RATES);
43
+ } catch (error) { throw new APIError(`Failed to fetch inflation data: ${error}`); }
44
+ }
45
+ if (!cached?.length) throw new DataNotAvailableError(`No data available for ${type}`);
46
+ let result = [...cached];
47
+ if (start) result = result.filter(d => d.date >= new Date(start));
48
+ if (end) result = result.filter(d => d.date <= new Date(end));
49
+ if (limit && limit > 0) result = result.slice(0, limit);
50
+ return result;
51
+ }
52
+
53
+ async calculateInflation(options: { startYear: number; startMonth: number; endYear: number; endMonth: number; basketValue: number; }): Promise<InflationCalculation> {
54
+ const { startYear, startMonth, endYear, endMonth, basketValue } = options;
55
+ const cacheKey = `tcmb:calc:${startYear}-${startMonth}:${endYear}-${endMonth}:${basketValue}`;
56
+ const cached = this.cacheGet<InflationCalculation>(cacheKey);
57
+ if (cached) return cached;
58
+
59
+ try {
60
+ const response = await this.post<CalcApiResponse>(TcmbProvider.CALC_API_URL, { baslangicYil: String(startYear), baslangicAy: String(startMonth), bitisYil: String(endYear), bitisAy: String(endMonth), malSepeti: String(basketValue) }, { headers: { 'Accept': '*/*', 'Content-Type': 'application/json', 'Origin': 'https://herkesicin.tcmb.gov.tr', 'Referer': 'https://herkesicin.tcmb.gov.tr/' } });
61
+ const data = response.data;
62
+ const parseFloat = (v: string) => { const c = String(v || '').replace(/,/g, ''); const n = Number.parseFloat(c); return isNaN(n) ? 0 : n; };
63
+ const result: InflationCalculation = { startDate: `${startYear}-${String(startMonth).padStart(2, '0')}`, endDate: `${endYear}-${String(endMonth).padStart(2, '0')}`, initialValue: basketValue, finalValue: parseFloat(data?.yeniSepetDeger ?? ''), totalYears: data?.toplamYil ?? 0, totalMonths: data?.toplamAy ?? 0, totalChange: parseFloat(data?.toplamDegisim ?? ''), avgYearlyInflation: parseFloat(data?.ortalamaYillikEnflasyon ?? ''), startCpi: parseFloat(data?.ilkYilTufe ?? ''), endCpi: parseFloat(data?.sonYilTufe ?? '') };
64
+ this.cacheSet(cacheKey, result, TTL.INFLATION_DATA);
65
+ return result;
66
+ } catch (error) { throw new APIError(`Failed to calculate inflation: ${error}`); }
67
+ }
68
+
69
+ private parseInflationTable(html: string): InflationData[] {
70
+ const $ = cheerio.load(html);
71
+ const inflationData: InflationData[] = [];
72
+ const tables = $('table').toArray();
73
+ for (const table of tables) {
74
+ const headerText = $(table).find('tr').first().text().toLowerCase();
75
+ if (!headerText.includes('tüfe') && !headerText.includes('üfe') && !headerText.includes('enflasyon') && !headerText.includes('yıllık')) continue;
76
+ $(table).find('tr').slice(1).each((_, row) => {
77
+ const cells = $(row).find('td, th').map((_, c) => $(c).text().trim()).get();
78
+ if (cells.length < 3 || !cells[0]) return;
79
+ const [dateStr, yearlyStr, monthlyStr] = cells.length >= 5 ? [cells[0], cells[2], cells[4] || ''] : [cells[0], cells[1], cells[2]];
80
+ const match = dateStr.replace(/[.,]/g, '').match(/(\d{1,2})-(\d{4})/);
81
+ if (!match) return;
82
+ const [, month, year] = match;
83
+ const date = new Date(parseInt(year), parseInt(month) - 1, 1);
84
+ const yearly = parseFloat(yearlyStr.replace('%', '').replace(',', '.').replace(/[^\d.-]/g, ''));
85
+ const monthly = parseFloat(monthlyStr.replace('%', '').replace(',', '.').replace(/[^\d.-]/g, '')) || 0;
86
+ if (!isNaN(yearly)) inflationData.push({ date, yearMonth: dateStr, yearlyInflation: yearly, monthlyInflation: monthly });
87
+ });
88
+ if (inflationData.length > 0) break;
89
+ }
90
+ inflationData.sort((a, b) => b.date.getTime() - a.date.getTime());
91
+ return inflationData;
92
+ }
93
+ }
94
+
95
+ let provider: TcmbProvider | null = null;
96
+ export function getTcmbProvider(): TcmbProvider { if (!provider) provider = new TcmbProvider(); return provider; }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * TEFAS provider for mutual fund data.
3
+ */
4
+ import axios, { AxiosInstance } from 'axios';
5
+ import https from 'https';
6
+ import { BaseProvider, ProviderOptions } from './base.js';
7
+ import { TTL } from '../cache.js';
8
+ import { APIError, DataNotAvailableError } from '../exceptions.js';
9
+
10
+ export interface FundInfo { fundCode: string; name: string; date: string; price: number; fundSize: number; investorCount: number; founder?: string; manager?: string; fundType?: string; category?: string; riskValue?: number; dailyReturn?: number; return1m?: number; return3m?: number; return6m?: number; returnYtd?: number; return1y?: number; return3y?: number; return5y?: number; }
11
+ export interface FundHistoryData { date: Date; price: number; fundSize: number; investors: number; }
12
+ export interface HistoryOptions { period?: string; start?: Date; end?: Date; }
13
+ export interface SearchResult { fundCode: string; name: string; fundType?: string; return1y?: number; }
14
+
15
+ interface FundApiResponse { fundInfo?: Array<{ FONUNVAN?: string; TARIH?: string; SONFIYAT?: number; PORTBUYUKLUK?: number; YATIRIMCISAYI?: number; KURUCU?: string; YONETICI?: string; FONTUR?: string; FONKATEGORI?: string; RISKDEGERI?: number; GUNLUKGETIRI?: number; }>; fundReturn?: Array<{ GETIRI1A?: number; GETIRI3A?: number; GETIRI6A?: number; GETIRIYB?: number; GETIRI1Y?: number; GETIRI3Y?: number; GETIRI5Y?: number; }>; }
16
+ interface HistoryApiResponse { data?: Array<{ TARIH?: number; FIYAT?: number; PORTFOYBUYUKLUK?: number; KISISAYISI?: number; }>; }
17
+ interface ComparisonApiResponse { data?: Array<{ FONKODU?: string; FONUNVAN?: string; FONTURACIKLAMA?: string; GETIRI1Y?: number; }>; }
18
+
19
+ export class TefasProvider extends BaseProvider {
20
+ private static readonly BASE_URL = 'https://www.tefas.gov.tr/api/DB';
21
+ private static readonly PERIOD_DAYS: Record<string, number> = { '1d': 1, '5d': 5, '1mo': 30, '3mo': 90, '6mo': 180, '1y': 365 };
22
+ private unsafeClient: AxiosInstance;
23
+
24
+ constructor(options?: ProviderOptions) {
25
+ super(options);
26
+ this.unsafeClient = axios.create({ timeout: 30000, httpsAgent: new https.Agent({ rejectUnauthorized: false }), headers: { 'User-Agent': 'Mozilla/5.0' } });
27
+ }
28
+
29
+ private formatDate(date: Date): string { return `${String(date.getDate()).padStart(2, '0')}.${String(date.getMonth() + 1).padStart(2, '0')}.${date.getFullYear()}`; }
30
+
31
+ async getFundDetail(fundCode: string): Promise<FundInfo> {
32
+ fundCode = fundCode.toUpperCase();
33
+ const cacheKey = `tefas:detail:${fundCode}`;
34
+ const cached = this.cacheGet<FundInfo>(cacheKey);
35
+ if (cached) return cached;
36
+
37
+ try {
38
+ const response = await this.unsafeClient.post<FundApiResponse>(`${TefasProvider.BASE_URL}/GetAllFundAnalyzeData`, new URLSearchParams({ dil: 'TR', fonkod: fundCode }).toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8' } });
39
+ const data = response.data;
40
+ if (!data?.fundInfo?.length) throw new DataNotAvailableError(`No data for fund: ${fundCode}`);
41
+ const fi = data.fundInfo[0]; const fr = data.fundReturn?.[0] ?? {};
42
+ const result: FundInfo = { fundCode, name: fi.FONUNVAN ?? '', date: fi.TARIH ?? '', price: Number(fi.SONFIYAT) || 0, fundSize: Number(fi.PORTBUYUKLUK) || 0, investorCount: Number(fi.YATIRIMCISAYI) || 0, founder: fi.KURUCU, manager: fi.YONETICI, fundType: fi.FONTUR, category: fi.FONKATEGORI, riskValue: fi.RISKDEGERI, dailyReturn: fi.GUNLUKGETIRI, return1m: fr.GETIRI1A, return3m: fr.GETIRI3A, return6m: fr.GETIRI6A, returnYtd: fr.GETIRIYB, return1y: fr.GETIRI1Y, return3y: fr.GETIRI3Y, return5y: fr.GETIRI5Y };
43
+ this.cacheSet(cacheKey, result, TTL.FUND_DATA);
44
+ return result;
45
+ } catch (error) { if (error instanceof DataNotAvailableError) throw error; throw new APIError(`Failed to fetch fund detail for ${fundCode}: ${error}`); }
46
+ }
47
+
48
+ async getHistory(fundCode: string, options: HistoryOptions = {}): Promise<FundHistoryData[]> {
49
+ fundCode = fundCode.toUpperCase();
50
+ const { period = '1mo', start, end } = options;
51
+ const endDt = end ?? new Date();
52
+ const startDt = start ?? new Date(endDt.getTime() - (TefasProvider.PERIOD_DAYS[period] ?? 30) * 24 * 60 * 60 * 1000);
53
+ const cacheKey = `tefas:history:${fundCode}:${startDt.toISOString()}:${endDt.toISOString()}`;
54
+ const cached = this.cacheGet<FundHistoryData[]>(cacheKey);
55
+ if (cached) return cached;
56
+
57
+ try {
58
+ const formData = new URLSearchParams({ fontip: 'YAT', sfontur: '', fonkod: fundCode, fongrup: '', bastarih: this.formatDate(startDt), bittarih: this.formatDate(endDt), fonturkod: '', fonunvantip: '', kurucukod: '' });
59
+ const response = await this.unsafeClient.post<HistoryApiResponse>(`${TefasProvider.BASE_URL}/BindHistoryInfo`, formData.toString(), { headers: { 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Origin': 'https://www.tefas.gov.tr', 'X-Requested-With': 'XMLHttpRequest' } });
60
+ const data = response.data?.data;
61
+ if (!data?.length) throw new DataNotAvailableError(`No history for fund: ${fundCode}`);
62
+ const records: FundHistoryData[] = data.filter(i => i.TARIH && i.TARIH > 0).map(i => ({ date: new Date(i.TARIH!), price: Number(i.FIYAT) || 0, fundSize: Number(i.PORTFOYBUYUKLUK) || 0, investors: Number(i.KISISAYISI) || 0 }));
63
+ records.sort((a, b) => a.date.getTime() - b.date.getTime());
64
+ this.cacheSet(cacheKey, records, TTL.FUND_DATA);
65
+ return records;
66
+ } catch (error) { if (error instanceof DataNotAvailableError) throw error; throw new APIError(`Failed to fetch history for ${fundCode}: ${error}`); }
67
+ }
68
+
69
+ async search(query: string, limit: number = 20): Promise<SearchResult[]> {
70
+ const cacheKey = `tefas:search:${query}:${limit}`;
71
+ const cached = this.cacheGet<SearchResult[]>(cacheKey);
72
+ if (cached) return cached;
73
+ try {
74
+ const formData = new URLSearchParams({ calismatipi: '2', fontip: 'YAT', sfontur: 'Tümü', kurucukod: '', fongrup: '', bastarih: 'Başlangıç', bittarih: 'Bitiş', fonturkod: '', fonunvantip: '', strperiod: '1,1,1,1,1,1,1', islemdurum: '1' });
75
+ const response = await this.unsafeClient.post<ComparisonApiResponse>(`${TefasProvider.BASE_URL}/BindComparisonFundReturns`, formData.toString(), { headers: { 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest' } });
76
+ const allFunds = response.data?.data ?? []; const queryLower = query.toLowerCase();
77
+ const matching: SearchResult[] = allFunds.filter(f => (f.FONKODU ?? '').toLowerCase().includes(queryLower) || (f.FONUNVAN ?? '').toLowerCase().includes(queryLower)).slice(0, limit).map(f => ({ fundCode: f.FONKODU ?? '', name: f.FONUNVAN ?? '', fundType: f.FONTURACIKLAMA, return1y: f.GETIRI1Y }));
78
+ this.cacheSet(cacheKey, matching, TTL.FUND_DATA);
79
+ return matching;
80
+ } catch { return []; }
81
+ }
82
+ }
83
+
84
+ let provider: TefasProvider | null = null;
85
+ export function getTefasProvider(): TefasProvider { if (!provider) provider = new TefasProvider(); return provider; }
package/src/ticker.ts ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Ticker class for stock data.
3
+ */
4
+ import { getParaticProvider, QuoteData, OHLCVData, HistoryOptions } from './providers/paratic.js';
5
+
6
+ export interface TickerInfo extends QuoteData { type: 'stock'; }
7
+
8
+ export class Ticker {
9
+ private readonly _symbol: string;
10
+ private _infoCache: TickerInfo | null = null;
11
+
12
+ constructor(symbol: string) { this._symbol = symbol.toUpperCase().replace('.IS', '').replace('.E', ''); }
13
+ get symbol(): string { return this._symbol; }
14
+
15
+ async getInfo(): Promise<TickerInfo> {
16
+ if (!this._infoCache) {
17
+ const quote = await getParaticProvider().getQuote(this._symbol);
18
+ this._infoCache = { ...quote, type: 'stock' };
19
+ }
20
+ return this._infoCache;
21
+ }
22
+
23
+ async getFastInfo(): Promise<TickerInfo> { return this.getInfo(); }
24
+
25
+ async getHistory(options: HistoryOptions = {}): Promise<OHLCVData[]> {
26
+ return getParaticProvider().getHistory(this._symbol, options);
27
+ }
28
+
29
+ toString(): string { return `Ticker('${this._symbol}')`; }
30
+ }
package/src/viop.ts ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * VIOP class for Turkish derivatives market data.
3
+ */
4
+ import { getViopProvider, ContractData } from './providers/kap.js';
5
+
6
+ export class VIOP {
7
+ private _futuresCache: ContractData[] | null = null;
8
+ private _optionsCache: ContractData[] | null = null;
9
+
10
+ async getFutures(): Promise<ContractData[]> {
11
+ if (!this._futuresCache) this._futuresCache = await getViopProvider().getFutures('all');
12
+ return this._futuresCache;
13
+ }
14
+
15
+ async getStockFutures(): Promise<ContractData[]> { return getViopProvider().getFutures('stock'); }
16
+ async getIndexFutures(): Promise<ContractData[]> { return getViopProvider().getFutures('index'); }
17
+ async getCurrencyFutures(): Promise<ContractData[]> { return getViopProvider().getFutures('currency'); }
18
+ async getCommodityFutures(): Promise<ContractData[]> { return getViopProvider().getFutures('commodity'); }
19
+
20
+ async getOptions(): Promise<ContractData[]> {
21
+ if (!this._optionsCache) this._optionsCache = await getViopProvider().getOptions('all');
22
+ return this._optionsCache;
23
+ }
24
+
25
+ async getStockOptions(): Promise<ContractData[]> { return getViopProvider().getOptions('stock'); }
26
+ async getIndexOptions(): Promise<ContractData[]> { return getViopProvider().getOptions('index'); }
27
+
28
+ async getBySymbol(symbol: string): Promise<ContractData[]> {
29
+ symbol = symbol.toUpperCase();
30
+ const [futures, options] = await Promise.all([this.getFutures(), this.getOptions()]);
31
+ return [...futures, ...options].filter(i => i.contract.toUpperCase().includes(symbol) || i.code.toUpperCase().includes(symbol));
32
+ }
33
+
34
+ toString(): string { return 'VIOP()'; }
35
+ }
package/test/demo.ts ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Test script for borsajs APIs
3
+ * Run with: npx tsx test/demo.ts
4
+ */
5
+ import { Crypto, cryptoPairs } from '../src/crypto.js';
6
+ import { FX } from '../src/fx.js';
7
+ import { Ticker } from '../src/ticker.js';
8
+ import { Index, indices } from '../src/index-class.js';
9
+ import { Fund, searchFunds } from '../src/fund.js';
10
+ import { Inflation } from '../src/inflation.js';
11
+
12
+ async function testCrypto() {
13
+ console.log('\nšŸŖ™ Testing Crypto (BtcTurk)...');
14
+ console.log('─'.repeat(50));
15
+ try {
16
+ const btc = new Crypto('BTCTRY');
17
+ const current = await btc.getCurrent();
18
+ console.log('BTC/TRY Current:', JSON.stringify(current, null, 2));
19
+ const pairs = await cryptoPairs('TRY');
20
+ console.log(`\nAvailable TRY pairs: ${pairs.slice(0, 10).join(', ')}...`);
21
+ } catch (error) { console.error('Crypto Error:', error); }
22
+ }
23
+
24
+ async function testFX() {
25
+ console.log('\nšŸ’± Testing FX (doviz.com)...');
26
+ console.log('─'.repeat(50));
27
+ try {
28
+ const usd = new FX('USD');
29
+ const current = await usd.getCurrent();
30
+ console.log('USD/TRY Current:', JSON.stringify(current, null, 2));
31
+ const gold = new FX('gram-altin');
32
+ const goldCurrent = await gold.getCurrent();
33
+ console.log('\nGold (gram) Current:', JSON.stringify(goldCurrent, null, 2));
34
+ } catch (error) { console.error('FX Error:', error); }
35
+ }
36
+
37
+ async function testTicker() {
38
+ console.log('\nšŸ“ˆ Testing Ticker (Paratic)...');
39
+ console.log('─'.repeat(50));
40
+ try {
41
+ const stock = new Ticker('THYAO');
42
+ const info = await stock.getInfo();
43
+ console.log('THYAO Info:', JSON.stringify(info, null, 2));
44
+ } catch (error) { console.error('Ticker Error:', error); }
45
+ }
46
+
47
+ async function testIndex() {
48
+ console.log('\nšŸ“Š Testing Index (Paratic)...');
49
+ console.log('─'.repeat(50));
50
+ try {
51
+ console.log('Available indices:', indices().slice(0, 10).join(', '));
52
+ const xu100 = new Index('XU100');
53
+ const info = await xu100.getInfo();
54
+ console.log('\nXU100 Info:', JSON.stringify(info, null, 2));
55
+ } catch (error) { console.error('Index Error:', error); }
56
+ }
57
+
58
+ async function testFund() {
59
+ console.log('\nšŸ¦ Testing Fund (TEFAS)...');
60
+ console.log('─'.repeat(50));
61
+ try {
62
+ const results = await searchFunds('ak', 5);
63
+ console.log('Search "ak":', JSON.stringify(results, null, 2));
64
+ if (results.length > 0) {
65
+ const fund = new Fund(results[0].fundCode);
66
+ const info = await fund.getInfo();
67
+ console.log(`\n${results[0].fundCode} Info:`, JSON.stringify(info, null, 2));
68
+ }
69
+ } catch (error) { console.error('Fund Error:', error); }
70
+ }
71
+
72
+ async function testInflation() {
73
+ console.log('\nšŸ“‰ Testing Inflation (TCMB)...');
74
+ console.log('─'.repeat(50));
75
+ try {
76
+ const inflation = new Inflation();
77
+ const latest = await inflation.getLatest();
78
+ console.log('Latest CPI:', JSON.stringify(latest, null, 2));
79
+ const cpi = await inflation.getTufe({ limit: 3 });
80
+ console.log('\nCPI last 3 months:', JSON.stringify(cpi, null, 2));
81
+ const calculation = await inflation.calculate(100000, '2020-01', '2024-01');
82
+ console.log('\n100,000 TL (2020-01 → 2024-01):', JSON.stringify(calculation, null, 2));
83
+ } catch (error) { console.error('Inflation Error:', error); }
84
+ }
85
+
86
+ async function main() {
87
+ console.log('šŸš€ borsajs API Test');
88
+ console.log('═'.repeat(50));
89
+ await testCrypto();
90
+ await testFX();
91
+ await testTicker();
92
+ await testIndex();
93
+ await testFund();
94
+ await testInflation();
95
+ console.log('\n' + '═'.repeat(50));
96
+ console.log('āœ… All tests completed!');
97
+ }
98
+
99
+ main().catch(console.error);