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
JavaScript
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¤cies=${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¤cies=${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
};