UNPKG

somali-exchange-rates

Version:

πŸ‡ΈπŸ‡΄ Comprehensive Somali Exchange Rates platform with real-time rates, transfer fees, alerts, multi-language support, and advanced financial tools

567 lines (558 loc) β€’ 19.8 kB
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); // src/utils.ts import fs from "fs/promises"; import path from "path"; async function tryReadJSON(p) { try { const raw = await fs.readFile(p, "utf8"); return JSON.parse(raw); } catch { return null; } } async function tryWriteJSON(p, data) { try { await fs.mkdir(path.dirname(p), { recursive: true }); await fs.writeFile(p, JSON.stringify(data, null, 2), "utf8"); } catch { } } function invert(baseToOthers, sosPerBase) { const out = {}; for (const [k, v] of Object.entries(baseToOthers)) { out[k] = v / sosPerBase; } return out; } function nice(n) { return Number(n.toFixed(n < 1 ? 4 : 2)); } // src/providers/exchangeratehost.ts var API = "https://api.exchangerate.host/latest"; var ExchangerateHostProvider = class { name = "exchangerate.host"; async fetchRatesSOS() { const symbols = ["SOS", "EUR", "GBP", "KES", "ETB", "AED", "SAR", "TRY", "CNY", "USD"].join(","); const url = `${API}?base=USD&symbols=${symbols}`; const res = await fetch(url); if (!res.ok) throw new Error(`Provider error ${res.status}`); const data = await res.json(); const usdTo = data.rates; const sosPerUsd = usdTo["SOS"]; if (!sosPerUsd) throw new Error("Provider returned no SOS rate"); return invert(usdTo, sosPerUsd); } }; // src/providers/fixer.ts var API_BASE = "https://api.fixer.io/v1"; var FixerProvider = class { constructor(apiKey) { this.apiKey = apiKey; } name = "fixer.io"; priority = 2; timeout = 5e3; async fetchRatesSOS() { const symbols = ["SOS", "EUR", "GBP", "KES", "ETB", "AED", "SAR", "TRY", "CNY", "USD"].join(","); const url = `${API_BASE}/latest?access_key=${this.apiKey}&base=USD&symbols=${symbols}`; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.timeout); try { const res = await fetch(url, { signal: controller.signal }); clearTimeout(timeoutId); if (!res.ok) throw new Error(`Fixer API error ${res.status}`); const data = await res.json(); if (!data.success) throw new Error(`Fixer API error: ${data.error?.info || "Unknown error"}`); const usdTo = data.rates; const sosPerUsd = usdTo["SOS"]; if (!sosPerUsd) throw new Error("Fixer returned no SOS rate"); return invert(usdTo, sosPerUsd); } catch (error) { clearTimeout(timeoutId); throw error; } } async fetchHistoricalRates(date) { const symbols = ["SOS", "EUR", "GBP", "KES", "ETB", "AED", "SAR", "TRY", "CNY", "USD"].join(","); const url = `${API_BASE}/${date}?access_key=${this.apiKey}&base=USD&symbols=${symbols}`; const res = await fetch(url); if (!res.ok) throw new Error(`Fixer historical API error ${res.status}`); const data = await res.json(); if (!data.success) throw new Error(`Fixer API error: ${data.error?.info || "Unknown error"}`); const usdTo = data.rates; const sosPerUsd = usdTo["SOS"]; if (!sosPerUsd) throw new Error("Fixer returned no SOS rate for date"); return invert(usdTo, sosPerUsd); } }; // src/providers/currencyapi.ts var API_BASE2 = "https://api.currencyapi.com/v3"; var CurrencyAPIProvider = class { constructor(apiKey) { this.apiKey = apiKey; } name = "currencyapi.com"; priority = 3; timeout = 5e3; async fetchRatesSOS() { const currencies = ["SOS", "EUR", "GBP", "KES", "ETB", "AED", "SAR", "TRY", "CNY", "USD"].join(","); const url = `${API_BASE2}/latest?apikey=${this.apiKey}&base_currency=USD&currencies=${currencies}`; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.timeout); try { const res = await fetch(url, { signal: controller.signal }); clearTimeout(timeoutId); if (!res.ok) throw new Error(`CurrencyAPI error ${res.status}`); const data = await res.json(); if (data.errors) throw new Error(`CurrencyAPI error: ${JSON.stringify(data.errors)}`); const rates = {}; for (const [currency, info] of Object.entries(data.data)) { rates[currency] = info.value; } const sosPerUsd = rates["SOS"]; if (!sosPerUsd) throw new Error("CurrencyAPI returned no SOS rate"); return invert(rates, sosPerUsd); } catch (error) { clearTimeout(timeoutId); throw error; } } async fetchHistoricalRates(date) { const currencies = ["SOS", "EUR", "GBP", "KES", "ETB", "AED", "SAR", "TRY", "CNY", "USD"].join(","); const url = `${API_BASE2}/historical?apikey=${this.apiKey}&base_currency=USD&currencies=${currencies}&date=${date}`; const res = await fetch(url); if (!res.ok) throw new Error(`CurrencyAPI historical error ${res.status}`); const data = await res.json(); if (data.errors) throw new Error(`CurrencyAPI error: ${JSON.stringify(data.errors)}`); const rates = {}; for (const [currency, info] of Object.entries(data.data)) { rates[currency] = info.value; } const sosPerUsd = rates["SOS"]; if (!sosPerUsd) throw new Error("CurrencyAPI returned no SOS rate for date"); return invert(rates, sosPerUsd); } }; // src/providers/manager.ts var ProviderManager = class { config; constructor(config) { this.config = { primary: new ExchangerateHostProvider(), fallbacks: [], timeout: 1e4, maxRetries: 3, ...config }; } async fetchRates() { const providers = [this.config.primary, ...this.config.fallbacks]; let lastError = null; for (const provider of providers) { for (let attempt = 1; attempt <= this.config.maxRetries; attempt++) { try { console.log(`Attempting to fetch rates from ${provider.name} (attempt ${attempt})`); const rates = await this.fetchWithTimeout(provider, this.config.timeout); console.log(`Successfully fetched rates from ${provider.name}`); return rates; } catch (error) { lastError = error; console.warn(`Failed to fetch from ${provider.name} (attempt ${attempt}):`, error); if (attempt < this.config.maxRetries) { const delay = Math.pow(2, attempt - 1) * 1e3; await new Promise((resolve) => setTimeout(resolve, delay)); } } } } throw new Error(`All providers failed. Last error: ${lastError?.message}`); } async fetchHistoricalRates(date) { const providers = [this.config.primary, ...this.config.fallbacks].filter((p) => p.fetchHistoricalRates); let lastError = null; for (const provider of providers) { try { if (provider.fetchHistoricalRates) { console.log(`Fetching historical rates from ${provider.name} for ${date}`); const rates = await provider.fetchHistoricalRates(date); console.log(`Successfully fetched historical rates from ${provider.name}`); return rates; } } catch (error) { lastError = error; console.warn(`Failed to fetch historical rates from ${provider.name}:`, error); } } throw new Error(`All providers failed for historical data. Last error: ${lastError?.message}`); } async fetchWithTimeout(provider, timeout) { return new Promise((resolve, reject) => { const timer = setTimeout(() => { reject(new Error(`Provider ${provider.name} timed out after ${timeout}ms`)); }, timeout); provider.fetchRatesSOS().then((rates) => { clearTimeout(timer); resolve(rates); }).catch((error) => { clearTimeout(timer); reject(error); }); }); } addFallbackProvider(provider) { this.config.fallbacks.push(provider); this.config.fallbacks.sort((a, b) => (a.priority || 999) - (b.priority || 999)); } setPrimaryProvider(provider) { this.config.primary = provider; } getProviderStatus() { const providers = [this.config.primary, ...this.config.fallbacks]; return providers.map((provider) => ({ name: provider.name, priority: provider.priority || 999, available: true // Could implement health checks here })); } }; function createProvider(name, apiKey) { switch (name.toLowerCase()) { case "exchangerate-host": return new ExchangerateHostProvider(); case "fixer": if (!apiKey) throw new Error("Fixer provider requires API key"); return new FixerProvider(apiKey); case "currencyapi": if (!apiKey) throw new Error("CurrencyAPI provider requires API key"); return new CurrencyAPIProvider(apiKey); default: throw new Error(`Unknown provider: ${name}`); } } // src/historical.ts import path2 from "path"; import os from "os"; var HistoricalRateService = class { providerManager; cachePath; constructor(providerManager) { this.providerManager = providerManager || new ProviderManager(); this.cachePath = path2.join(os.homedir(), ".sosx", "historical-cache.json"); } async getHistoricalRates(date) { const cached = await this.getCachedRates(); if (cached[date]) { console.log(`Using cached historical rates for ${date}`); return cached[date]; } try { const rates = await this.providerManager.fetchHistoricalRates(date); await this.cacheRates(date, rates); return rates; } catch (error) { console.warn(`Failed to fetch historical rates for ${date}:`, error); throw error; } } async getRateHistory(options) { const { from, to, currency, baseCurrency = "SOS" } = options; const startDate = new Date(from); const endDate = new Date(to); const results = []; const currentDate = new Date(startDate); while (currentDate <= endDate) { const dateStr = currentDate.toISOString().split("T")[0]; try { const rates = await this.getHistoricalRates(dateStr); const rate = baseCurrency === "SOS" ? rates[currency] : 1 / rates[currency]; results.push({ date: dateStr, rate }); } catch (error) { console.warn(`Skipping ${dateStr} due to error:`, error); } currentDate.setDate(currentDate.getDate() + 1); } return results; } async getVolatility(currency, period = "30d") { const days = parseInt(period.replace("d", "")); const endDate = /* @__PURE__ */ new Date(); const startDate = /* @__PURE__ */ new Date(); startDate.setDate(startDate.getDate() - days); const history = await this.getRateHistory({ from: startDate.toISOString().split("T")[0], to: endDate.toISOString().split("T")[0], currency }); if (history.length < 2) return 0; const returns = []; for (let i = 1; i < history.length; i++) { const dailyReturn = (history[i].rate - history[i - 1].rate) / history[i - 1].rate; returns.push(dailyReturn); } const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const variance = returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / returns.length; return Math.sqrt(variance) * Math.sqrt(365); } async getCachedRates() { const cached = await tryReadJSON(this.cachePath); return cached || {}; } async cacheRates(date, rates) { const cached = await this.getCachedRates(); cached[date] = rates; const cutoffDate = /* @__PURE__ */ new Date(); cutoffDate.setDate(cutoffDate.getDate() - 90); const cutoffStr = cutoffDate.toISOString().split("T")[0]; Object.keys(cached).forEach((date2) => { if (date2 < cutoffStr) { delete cached[date2]; } }); await tryWriteJSON(this.cachePath, cached); } }; var historicalService; async function getHistoricalRates(date, provider) { if (!historicalService) { const providerManager = provider ? new ProviderManager({ primary: provider }) : void 0; historicalService = new HistoricalRateService(providerManager); } return historicalService.getHistoricalRates(date); } async function getRateHistory(currency, from, to) { if (!historicalService) { historicalService = new HistoricalRateService(); } return historicalService.getRateHistory({ currency, from, to }); } async function getVolatility(currency, period = "30d") { if (!historicalService) { historicalService = new HistoricalRateService(); } return historicalService.getVolatility(currency, period); } // src/analysis.ts var MarketAnalyzer = class { async analyzeMarket(from, to, period = "30d") { const days = parseInt(period.replace("d", "")); const endDate = /* @__PURE__ */ new Date(); const startDate = /* @__PURE__ */ new Date(); startDate.setDate(startDate.getDate() - days); const history = await getRateHistory( to, startDate.toISOString().split("T")[0], endDate.toISOString().split("T")[0] ); if (history.length < 14) { throw new Error("Insufficient data for analysis (minimum 14 days required)"); } const rates = history.map((h) => h.rate); const volatility = await getVolatility(to, period); return { volatility, trend: this.calculateTrend(rates), support: this.calculateSupport(rates), resistance: this.calculateResistance(rates), rsi: this.calculateRSI(rates), sma: this.calculateSMA(rates, [7, 14, 30]), ema: this.calculateEMA(rates, [7, 14, 30]) }; } calculateTrend(rates) { if (rates.length < 2) return "neutral"; const recentRates = rates.slice(-7); const olderRates = rates.slice(-14, -7); const recentAvg = recentRates.reduce((sum, rate) => sum + rate, 0) / recentRates.length; const olderAvg = olderRates.reduce((sum, rate) => sum + rate, 0) / olderRates.length; const change = (recentAvg - olderAvg) / olderAvg; if (change > 0.02) return "bullish"; if (change < -0.02) return "bearish"; return "neutral"; } calculateSupport(rates) { const recentRates = rates.slice(-30); return Math.min(...recentRates); } calculateResistance(rates) { const recentRates = rates.slice(-30); return Math.max(...recentRates); } calculateRSI(rates, period = 14) { if (rates.length < period + 1) return 50; const changes = []; for (let i = 1; i < rates.length; i++) { changes.push(rates[i] - rates[i - 1]); } const recentChanges = changes.slice(-period); const gains = recentChanges.filter((change) => change > 0); const losses = recentChanges.filter((change) => change < 0).map((loss) => Math.abs(loss)); const avgGain = gains.length > 0 ? gains.reduce((sum, gain) => sum + gain, 0) / gains.length : 0; const avgLoss = losses.length > 0 ? losses.reduce((sum, loss) => sum + loss, 0) / losses.length : 0; if (avgLoss === 0) return 100; const rs = avgGain / avgLoss; return 100 - 100 / (1 + rs); } calculateSMA(rates, periods) { return periods.map((period) => { if (rates.length < period) return rates[rates.length - 1] || 0; const recentRates = rates.slice(-period); return recentRates.reduce((sum, rate) => sum + rate, 0) / recentRates.length; }); } calculateEMA(rates, periods) { return periods.map((period) => { if (rates.length < period) return rates[rates.length - 1] || 0; const multiplier = 2 / (period + 1); let ema = rates[0]; for (let i = 1; i < rates.length; i++) { ema = rates[i] * multiplier + ema * (1 - multiplier); } return ema; }); } }; var SomaliaMarketService = class { async getSomaliaMarketData() { const baseRate = 570; return { regions: { mogadishu: { officialRate: baseRate, blackMarketRate: baseRate * 1.05, // 5% premium spread: 0.05, volume: 1e6, // Daily volume in USD lastUpdated: /* @__PURE__ */ new Date() }, hargeisa: { officialRate: baseRate, blackMarketRate: baseRate * 1.08, // 8% premium spread: 0.08, volume: 5e5, lastUpdated: /* @__PURE__ */ new Date() }, bosaso: { officialRate: baseRate, blackMarketRate: baseRate * 1.12, // 12% premium spread: 0.12, volume: 2e5, lastUpdated: /* @__PURE__ */ new Date() }, kismayo: { officialRate: baseRate, blackMarketRate: baseRate * 1.15, // 15% premium spread: 0.15, volume: 15e4, lastUpdated: /* @__PURE__ */ new Date() }, garowe: { officialRate: baseRate, blackMarketRate: baseRate * 1.1, // 10% premium spread: 0.1, volume: 1e5, lastUpdated: /* @__PURE__ */ new Date() } } }; } async getRegionalSpread(region) { const marketData = await this.getSomaliaMarketData(); const regionData = marketData.regions[region.toLowerCase()]; if (!regionData) { throw new Error(`Unknown region: ${region}`); } return regionData.spread; } async getBestRegionalRate(amount) { const marketData = await this.getSomaliaMarketData(); let bestRegion = ""; let bestRate = 0; let bestAmount = 0; for (const [region, data] of Object.entries(marketData.regions)) { const rate = data.blackMarketRate || data.officialRate; const convertedAmount = amount * rate; if (convertedAmount > bestAmount) { bestRegion = region; bestRate = rate; bestAmount = convertedAmount; } } const worstAmount = Math.min(...Object.values(marketData.regions).map( (data) => amount * (data.blackMarketRate || data.officialRate) )); const savings = bestAmount - worstAmount; return { region: bestRegion, rate: bestRate, savings }; } }; var marketAnalyzer; var somaliaMarketService; async function analyzeMarket(from, to, period) { if (!marketAnalyzer) { marketAnalyzer = new MarketAnalyzer(); } return marketAnalyzer.analyzeMarket(from, to, period); } async function getSomaliaMarketData() { if (!somaliaMarketService) { somaliaMarketService = new SomaliaMarketService(); } return somaliaMarketService.getSomaliaMarketData(); } async function detectAnomalies(from, to, options) { const { threshold, timeWindow } = options; const days = parseInt(timeWindow.replace(/[^\d]/g, "")); const endDate = /* @__PURE__ */ new Date(); const startDate = /* @__PURE__ */ new Date(); startDate.setDate(startDate.getDate() - days); const history = await getRateHistory( to, startDate.toISOString().split("T")[0], endDate.toISOString().split("T")[0] ); if (history.length < 2) { return { anomaly: false, deviation: 0, message: "Insufficient data" }; } const currentRate = history[history.length - 1].rate; const previousRate = history[history.length - 2].rate; const change = Math.abs((currentRate - previousRate) / previousRate); const anomaly = change > threshold; const message = anomaly ? `Anomaly detected: ${(change * 100).toFixed(2)}% change in ${from}/${to} rate` : "No anomaly detected"; return { anomaly, deviation: change, message }; } export { __require, tryReadJSON, tryWriteJSON, nice, ExchangerateHostProvider, ProviderManager, createProvider, HistoricalRateService, getHistoricalRates, getRateHistory, getVolatility, MarketAnalyzer, SomaliaMarketService, analyzeMarket, getSomaliaMarketData, detectAnomalies };