somali-exchange-rates
Version:
πΈπ΄ Comprehensive Somali Exchange Rates platform with real-time rates, transfer fees, alerts, multi-language support, and advanced financial tools
754 lines (739 loc) β’ 23.1 kB
JavaScript
import {
RateStreamServer,
calculateTransferFee,
compareTransferOptions,
convert,
createRateStream,
exportAnalysisReport,
exportRates,
getBestTransferOption,
getRates,
getUserConfig,
listRateAlerts,
loadUserConfig,
quote,
removeRateAlert,
runConfigWizard,
setRateAlert,
startAlertMonitoring
} from "./chunk-A7YWPFGN.mjs";
import {
__require,
analyzeMarket,
getHistoricalRates,
getRateHistory,
getSomaliaMarketData,
getVolatility
} from "./chunk-GBQMRN7V.mjs";
// src/tui.ts
import * as blessed from "blessed";
import * as contrib from "blessed-contrib";
var ExchangeRateTUI = class {
screen;
grid;
rateTable;
chartLine;
alertsList;
logBox;
statusBar;
currentRates = {};
selectedCurrency = "USD";
constructor() {
this.screen = blessed.screen({
smartCSR: true,
title: "Somali Exchange Rates Monitor"
});
this.setupLayout();
this.setupEventHandlers();
this.startDataUpdates();
}
setupLayout() {
this.grid = new contrib.grid({ rows: 12, cols: 12, screen: this.screen });
this.rateTable = this.grid.set(0, 0, 6, 6, blessed.table, {
keys: true,
fg: "white",
selectedFg: "white",
selectedBg: "blue",
interactive: true,
label: "Exchange Rates (1 SOS = X)",
width: "50%",
height: "50%",
border: { type: "line", fg: "cyan" },
columnSpacing: 2,
columnWidth: [8, 12, 10]
});
this.chartLine = this.grid.set(0, 6, 6, 6, contrib.line, {
style: {
line: "yellow",
text: "green",
baseline: "black"
},
xLabelPadding: 3,
xPadding: 5,
label: `${this.selectedCurrency}/SOS Rate History`,
border: { type: "line", fg: "cyan" }
});
this.alertsList = this.grid.set(6, 0, 4, 6, blessed.list, {
keys: true,
fg: "white",
selectedFg: "white",
selectedBg: "green",
interactive: true,
label: "Active Alerts",
border: { type: "line", fg: "cyan" },
scrollable: true,
alwaysScroll: true,
scrollbar: {
ch: " ",
track: {
bg: "cyan"
},
style: {
inverse: true
}
}
});
this.logBox = this.grid.set(6, 6, 4, 6, blessed.log, {
fg: "green",
selectedFg: "green",
label: "Activity Log",
border: { type: "line", fg: "cyan" },
scrollable: true,
alwaysScroll: true,
scrollbar: {
ch: " ",
track: {
bg: "cyan"
},
style: {
inverse: true
}
}
});
this.statusBar = this.grid.set(10, 0, 2, 12, blessed.text, {
content: "Press q to quit, r to refresh, c to change currency, a to view alerts",
style: {
fg: "white",
bg: "blue"
},
height: 2
});
}
setupEventHandlers() {
this.screen.key(["escape", "q", "C-c"], () => {
return process.exit(0);
});
this.screen.key(["r"], async () => {
await this.refreshData();
});
this.screen.key(["c"], () => {
this.showCurrencySelector();
});
this.screen.key(["a"], async () => {
await this.refreshAlerts();
});
this.rateTable.on("select", (item) => {
const currency = item.content.split(" ")[0];
if (currency && currency !== "Currency") {
this.selectedCurrency = currency;
this.updateChart();
this.log(`Selected currency: ${currency}`);
}
});
this.alertsList.on("select", (item) => {
this.log(`Selected alert: ${item.content}`);
});
}
async startDataUpdates() {
await this.refreshData();
await this.refreshAlerts();
await this.updateChart();
try {
const stream = createRateStream(["USD", "EUR", "GBP", "KES", "ETB"]);
stream.on("rate-updates", (updates) => {
updates.forEach((update) => {
this.log(`Rate update: ${update.to} = ${update.rate.toFixed(6)} (${update.changePercent > 0 ? "+" : ""}${update.changePercent.toFixed(2)}%)`);
});
this.refreshData();
});
stream.on("error", (error) => {
this.log(`Stream error: ${error.message}`);
});
this.log("Connected to real-time rate stream");
} catch (error) {
this.log("Real-time stream not available, using periodic updates");
setInterval(async () => {
await this.refreshData();
}, 6e4);
}
}
async refreshData() {
try {
this.log("Refreshing exchange rates...");
const rates = await getRates();
this.currentRates = rates;
this.updateRateTable(rates);
this.log("Exchange rates updated");
} catch (error) {
this.log(`Error refreshing rates: ${error}`);
}
}
updateRateTable(rates) {
const data = [["Currency", "Rate", "Change"]];
Object.entries(rates).forEach(([currency, rate]) => {
if (currency === "SOS") return;
const formattedRate = rate.toFixed(6);
const change = "\u2014";
data.push([currency, formattedRate, change]);
});
this.rateTable.setData(data);
this.screen.render();
}
async updateChart() {
try {
this.log(`Loading chart data for ${this.selectedCurrency}...`);
const endDate = /* @__PURE__ */ new Date();
const startDate = /* @__PURE__ */ new Date();
startDate.setDate(startDate.getDate() - 30);
const history = await getRateHistory(
this.selectedCurrency,
startDate.toISOString().split("T")[0],
endDate.toISOString().split("T")[0]
);
const chartData = {
title: this.selectedCurrency,
style: { line: "red" },
x: history.map((h) => h.date.slice(-5)),
// Show MM-DD
y: history.map((h) => h.rate)
};
this.chartLine.setData([chartData]);
this.chartLine.setLabel(`${this.selectedCurrency}/SOS Rate History (30 days)`);
this.screen.render();
this.log(`Chart updated for ${this.selectedCurrency}`);
} catch (error) {
this.log(`Error updating chart: ${error}`);
}
}
async refreshAlerts() {
try {
const alerts = await listRateAlerts();
const alertItems = alerts.filter((alert) => alert.active).map((alert) => `${alert.from}/${alert.to} ${alert.direction} ${alert.threshold}`);
this.alertsList.setItems(alertItems);
this.screen.render();
this.log(`Loaded ${alertItems.length} active alerts`);
} catch (error) {
this.log(`Error loading alerts: ${error}`);
}
}
showCurrencySelector() {
const currencies = ["USD", "EUR", "GBP", "KES", "ETB", "AED", "SAR", "TRY", "CNY"];
const list2 = blessed.list({
parent: this.screen,
top: "center",
left: "center",
width: 20,
height: 12,
border: { type: "line" },
label: "Select Currency",
keys: true,
vi: true,
items: currencies,
style: {
selected: {
bg: "blue"
}
}
});
list2.on("select", (item) => {
this.selectedCurrency = item.content;
this.updateChart();
list2.destroy();
this.screen.render();
});
list2.on("keypress", (ch, key) => {
if (key.name === "escape") {
list2.destroy();
this.screen.render();
}
});
list2.focus();
this.screen.render();
}
log(message) {
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
this.logBox.log(`[${timestamp}] ${message}`);
this.screen.render();
}
run() {
this.screen.render();
this.log("Somali Exchange Rates Monitor started");
this.log("Press q to quit, r to refresh, c to change currency, a to view alerts");
}
};
function startTUI() {
const tui = new ExchangeRateTUI();
tui.run();
}
// src/cli.ts
function help() {
console.log(
`
\u{1F1F8}\u{1F1F4} sosx \u2014 Somali Exchange Rates (SOS)
BASIC USAGE:
sosx rate <FROM> <TO> [AMOUNT] Show conversion (default amount 1)
sosx quote <FROM> <TO> [AMOUNT] Pretty formatted conversion
sosx rates Show all current rates
HISTORICAL DATA:
sosx history <CURRENCY> [DAYS] Show rate history (default 30 days)
sosx historical <DATE> Get rates for specific date (YYYY-MM-DD)
sosx volatility <CURRENCY> [DAYS] Calculate volatility (default 30 days)
ALERTS & MONITORING:
sosx alert set <FROM> <TO> <THRESHOLD> <above|below> [--email EMAIL] [--webhook URL]
sosx alert list List all alerts
sosx alert remove <ID> Remove alert by ID
sosx monitor Start alert monitoring
sosx tui Launch interactive terminal UI
sosx stream [PORT] Start WebSocket rate stream server
TRANSFER FEES:
sosx transfer <AMOUNT> <FROM> <TO> <PROVIDER> <METHOD>
sosx compare <AMOUNT> <FROM> <TO> <METHOD>
sosx best <AMOUNT> <FROM> <TO> Find best transfer option
MARKET ANALYSIS:
sosx analyze <FROM> <TO> [PERIOD] Market analysis (default 30d)
sosx somalia Somalia regional market data
sosx anomaly <FROM> <TO> [THRESHOLD] [WINDOW]
EXPORT & REPORTING:
sosx export <FORMAT> <CURRENCIES> <PERIOD> [--output FILE]
sosx report <CURRENCIES> <PERIOD> [--output FILE]
CONFIGURATION:
sosx config Show current configuration
sosx config wizard Run configuration wizard
sosx config set <KEY> <VALUE> Set configuration value
sosx config language <LANGUAGE> Set language (en|so|ar)
EXAMPLES:
sosx rate USD SOS 100
sosx quote SOS USD 500000
sosx history USD 7
sosx alert set USD SOS 600 above --email user@example.com
sosx transfer 1000 USD SOS remitly mobile-money
sosx analyze USD SOS 30d
sosx export csv USD,EUR,GBP 30d
sosx tui
SUPPORTED CURRENCIES:
SOS (Somali Shilling), USD, EUR, GBP, KES, ETB, AED, SAR, TRY, CNY
TRANSFER PROVIDERS:
western-union, remitly, worldremit, wise
TRANSFER METHODS:
bank-transfer, cash-pickup, mobile-money
For more help: sosx help <command>
`.trim()
);
}
async function main() {
const args = process.argv.slice(2);
const [cmd, ...params] = args;
if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
if (params[0]) {
return showCommandHelp(params[0]);
}
return help();
}
try {
await loadUserConfig();
switch (cmd) {
case "rate":
await handleRate(params);
break;
case "quote":
await handleQuote(params);
break;
case "rates":
await handleRates();
break;
case "history":
await handleHistory(params);
break;
case "historical":
await handleHistorical(params);
break;
case "volatility":
await handleVolatility(params);
break;
case "alert":
await handleAlert(params);
break;
case "monitor":
await handleMonitor();
break;
case "tui":
startTUI();
break;
case "stream":
await handleStream(params);
break;
case "transfer":
await handleTransfer(params);
break;
case "compare":
await handleCompare(params);
break;
case "best":
await handleBest(params);
break;
case "analyze":
await handleAnalyze(params);
break;
case "somalia":
await handleSomalia();
break;
case "anomaly":
await handleAnomaly(params);
break;
case "export":
await handleExport(params);
break;
case "report":
await handleReport(params);
break;
case "config":
await handleConfig(params);
break;
default:
console.error(`Unknown command: ${cmd}`);
console.log("Run 'sosx help' for usage information.");
process.exit(1);
}
} catch (e) {
console.error("Error:", e.message || e);
process.exit(1);
}
}
async function handleRate(params) {
const [from, to, amountStr] = params;
if (!from || !to) {
console.error("Usage: sosx rate <FROM> <TO> [AMOUNT]");
return;
}
const amount = amountStr ? Number(amountStr) : 1;
const result = await convert(amount, from, to);
console.log(result);
}
async function handleQuote(params) {
const [from, to, amountStr] = params;
if (!from || !to) {
console.error("Usage: sosx quote <FROM> <TO> [AMOUNT]");
return;
}
const amount = amountStr ? Number(amountStr) : 1;
const result = await quote(from, to, amount);
console.log(result);
}
async function handleRates() {
const rates = await getRates();
console.log("Current exchange rates (1 SOS = X):");
Object.entries(rates).forEach(([currency, rate]) => {
if (currency !== "SOS") {
console.log(` ${currency}: ${rate.toFixed(6)}`);
}
});
}
async function handleHistory(params) {
const [currency, daysStr] = params;
if (!currency) {
console.error("Usage: sosx history <CURRENCY> [DAYS]");
return;
}
const days = daysStr ? parseInt(daysStr) : 30;
const endDate = /* @__PURE__ */ new Date();
const startDate = /* @__PURE__ */ new Date();
startDate.setDate(startDate.getDate() - days);
const history = await getRateHistory(
currency,
startDate.toISOString().split("T")[0],
endDate.toISOString().split("T")[0]
);
console.log(`Rate history for ${currency} (last ${days} days):`);
history.forEach((record) => {
console.log(` ${record.date}: ${record.rate.toFixed(6)}`);
});
}
async function handleHistorical(params) {
const [date] = params;
if (!date) {
console.error("Usage: sosx historical <DATE> (YYYY-MM-DD)");
return;
}
const rates = await getHistoricalRates(date);
console.log(`Exchange rates for ${date}:`);
Object.entries(rates).forEach(([currency, rate]) => {
if (currency !== "SOS") {
console.log(` ${currency}: ${rate.toFixed(6)}`);
}
});
}
async function handleVolatility(params) {
const [currency, daysStr] = params;
if (!currency) {
console.error("Usage: sosx volatility <CURRENCY> [DAYS]");
return;
}
const period = daysStr ? `${daysStr}d` : "30d";
const volatility = await getVolatility(currency, period);
console.log(`Volatility for ${currency} (${period}): ${(volatility * 100).toFixed(2)}%`);
}
async function handleAlert(params) {
const [action, ...args] = params;
switch (action) {
case "set":
const [from, to, thresholdStr, direction, ...flags] = args;
if (!from || !to || !thresholdStr || !direction) {
console.error("Usage: sosx alert set <FROM> <TO> <THRESHOLD> <above|below> [--email EMAIL] [--webhook URL]");
return;
}
const threshold = Number(thresholdStr);
const emailIndex = flags.indexOf("--email");
const webhookIndex = flags.indexOf("--webhook");
const email = emailIndex >= 0 ? flags[emailIndex + 1] : void 0;
const webhook = webhookIndex >= 0 ? flags[webhookIndex + 1] : void 0;
const newAlertId = await setRateAlert(
from,
to,
threshold,
direction,
{
...email && { email },
...webhook && { webhook }
}
);
console.log(`Alert created with ID: ${newAlertId}`);
break;
case "list":
const alerts = await listRateAlerts();
if (alerts.length === 0) {
console.log("No alerts configured.");
} else {
console.log("Active alerts:");
alerts.forEach((alert) => {
console.log(` ${alert.id}: ${alert.from}/${alert.to} ${alert.direction} ${alert.threshold} (${alert.active ? "active" : "inactive"})`);
});
}
break;
case "remove":
const [removeAlertId] = args;
if (!removeAlertId) {
console.error("Usage: sosx alert remove <ID>");
return;
}
await removeRateAlert(removeAlertId);
console.log(`Alert ${removeAlertId} removed.`);
break;
default:
console.error("Usage: sosx alert <set|list|remove> ...");
}
}
async function handleMonitor() {
console.log("Starting alert monitoring...");
startAlertMonitoring();
console.log("Alert monitoring started. Press Ctrl+C to stop.");
process.on("SIGINT", () => {
console.log("\nStopping alert monitoring...");
process.exit(0);
});
setInterval(() => {
}, 1e3);
}
async function handleStream(params) {
const port = params[0] ? parseInt(params[0]) : 8080;
console.log(`Starting WebSocket rate stream server on port ${port}...`);
const server = new RateStreamServer(port);
server.startUpdates();
console.log("Rate stream server started. Press Ctrl+C to stop.");
process.on("SIGINT", () => {
console.log("\nStopping rate stream server...");
server.close();
process.exit(0);
});
}
async function handleTransfer(params) {
const [amountStr, from, to, provider, method] = params;
if (!amountStr || !from || !to || !provider || !method) {
console.error("Usage: sosx transfer <AMOUNT> <FROM> <TO> <PROVIDER> <METHOD>");
return;
}
const result = await calculateTransferFee({
amount: Number(amountStr),
from,
to,
provider,
method
});
console.log(`Transfer Fee Calculation:`);
console.log(`Provider: ${result.provider}`);
console.log(`Fee: $${result.fee.toFixed(2)}`);
console.log(`Exchange Rate: ${result.exchangeRate.toFixed(6)}`);
console.log(`Total Cost: $${result.totalCost.toFixed(2)}`);
console.log(`Recipient Gets: ${result.recipientAmount.toFixed(2)}`);
console.log(`Estimated Time: ${result.estimatedTime}`);
}
async function handleCompare(params) {
const [amountStr, from, to, method] = params;
if (!amountStr || !from || !to || !method) {
console.error("Usage: sosx compare <AMOUNT> <FROM> <TO> <METHOD>");
return;
}
const results = await compareTransferOptions(
Number(amountStr),
from,
to,
method
);
console.log(`Transfer comparison for ${method}:`);
results.forEach((result, index) => {
console.log(`
${index + 1}. ${result.provider}:`);
console.log(` Fee: $${result.fee.toFixed(2)}`);
console.log(` Total Cost: $${result.totalCost.toFixed(2)}`);
console.log(` Recipient Gets: ${result.recipientAmount.toFixed(2)}`);
console.log(` Time: ${result.estimatedTime}`);
});
}
async function handleBest(params) {
const [amountStr, from, to] = params;
if (!amountStr || !from || !to) {
console.error("Usage: sosx best <AMOUNT> <FROM> <TO>");
return;
}
const { method, result } = await getBestTransferOption(
Number(amountStr),
from,
to
);
console.log(`Best transfer option:`);
console.log(`Method: ${method}`);
console.log(`Provider: ${result.provider}`);
console.log(`Fee: $${result.fee.toFixed(2)}`);
console.log(`Total Cost: $${result.totalCost.toFixed(2)}`);
console.log(`Recipient Gets: ${result.recipientAmount.toFixed(2)}`);
console.log(`Time: ${result.estimatedTime}`);
}
async function handleAnalyze(params) {
const [from, to, period] = params;
if (!from || !to) {
console.error("Usage: sosx analyze <FROM> <TO> [PERIOD]");
return;
}
const analysis = await analyzeMarket(from, to, period || "30d");
console.log(`Market Analysis for ${from}/${to}:`);
console.log(`Volatility: ${(analysis.volatility * 100).toFixed(2)}%`);
console.log(`Trend: ${analysis.trend}`);
console.log(`Support: ${analysis.support.toFixed(6)}`);
console.log(`Resistance: ${analysis.resistance.toFixed(6)}`);
console.log(`RSI: ${analysis.rsi.toFixed(2)}`);
console.log(`SMA (7/14/30): ${analysis.sma.map((v) => v.toFixed(6)).join(" / ")}`);
console.log(`EMA (7/14/30): ${analysis.ema.map((v) => v.toFixed(6)).join(" / ")}`);
}
async function handleSomalia() {
const data = await getSomaliaMarketData();
console.log("Somalia Regional Market Data:");
Object.entries(data.regions).forEach(([region, info]) => {
console.log(`
${region.toUpperCase()}:`);
console.log(` Official Rate: ${info.officialRate}`);
if (info.blackMarketRate) {
console.log(` Black Market Rate: ${info.blackMarketRate}`);
console.log(` Spread: ${(info.spread * 100).toFixed(1)}%`);
}
console.log(` Daily Volume: $${info.volume.toLocaleString()}`);
console.log(` Last Updated: ${info.lastUpdated.toLocaleString()}`);
});
}
async function handleAnomaly(params) {
const [from, to, thresholdStr, window] = params;
if (!from || !to) {
console.error("Usage: sosx anomaly <FROM> <TO> [THRESHOLD] [WINDOW]");
return;
}
const threshold = thresholdStr ? Number(thresholdStr) : 0.05;
const timeWindow = window || "1h";
const { detectAnomalies } = await import("./analysis-DXKAXUTL.mjs");
const result = await detectAnomalies(from, to, { threshold, timeWindow });
console.log(result.message);
if (result.anomaly) {
console.log(`Deviation: ${(result.deviation * 100).toFixed(2)}%`);
}
}
async function handleExport(params) {
const [format, currenciesStr, period, ...flags] = params;
if (!format || !currenciesStr || !period) {
console.error("Usage: sosx export <FORMAT> <CURRENCIES> <PERIOD> [--output FILE]");
return;
}
const currencies = currenciesStr.split(",");
const outputIndex = flags.indexOf("--output");
const output = outputIndex >= 0 ? flags[outputIndex + 1] : void 0;
const exportOptions = {
format,
currencies,
period
};
if (output) {
exportOptions.output = output;
}
const filename = await exportRates(exportOptions);
console.log(`Data exported to: ${filename}`);
}
async function handleReport(params) {
const [currenciesStr, period, ...flags] = params;
if (!currenciesStr || !period) {
console.error("Usage: sosx report <CURRENCIES> <PERIOD> [--output FILE]");
return;
}
const currencies = currenciesStr.split(",");
const outputIndex = flags.indexOf("--output");
const output = outputIndex >= 0 ? flags[outputIndex + 1] : void 0;
const filename = await exportAnalysisReport(currencies, period, output);
console.log(`Analysis report exported to: ${filename}`);
}
async function handleConfig(params) {
const [action, key, value] = params;
if (!action) {
const config = getUserConfig();
console.log("Current configuration:");
console.log(JSON.stringify(config, null, 2));
return;
}
switch (action) {
case "wizard":
await runConfigWizard();
break;
case "set":
if (!key || !value) {
console.error("Usage: sosx config set <KEY> <VALUE>");
return;
}
console.log(`Setting ${key} = ${value} (not implemented yet)`);
break;
case "language":
if (!key) {
console.error("Usage: sosx config language <LANGUAGE>");
return;
}
console.log(`Setting language to ${key} (not implemented yet)`);
break;
default:
console.error("Usage: sosx config [wizard|set|language]");
}
}
function showCommandHelp(command) {
console.log(`Help for command: ${command}`);
console.log("(Detailed help not implemented yet)");
}
if (__require.main === module) {
main();
}
export {
help,
main
};