uphold/index.js

110 lines
2.6 KiB
JavaScript

import fs from "node:fs";
import { parseArgs } from "node:util";
import { prefetchRates } from "./src/api.js";
import { Bot } from "./src/bot.js";
import logger from "./src/logger.js";
import { initDB, insertIntoDB, closePool } from "./src/db.js";
import { startServer } from "./src/server.js";
process.on("uncaughtException", (err) => {
logger.fatal(err, "Uncaught exception. Application crashing...");
process.exit(1);
});
process.on("unhandledRejection", (reason) => {
logger.fatal(reason, "Unhandled promise Rejection");
process.exit(1);
});
const options = {
pairs: {
type: "string",
short: "p",
default: process.env.PAIRS || "BTC-USD",
},
interval: {
type: "string",
short: "i",
default: process.env.INTERVAL || "5000",
},
threshold: {
type: "string",
short: "t",
default: process.env.THRESHOLD || "0.01",
},
};
const { values } = parseArgs({
options,
strict: true,
allowPositionals: true,
args: process.argv.slice(2),
});
const pairs = values.pairs.split(",").map((p) => p.trim());
const interval = parseInt(values.interval, 10);
const threshold = parseFloat(values.threshold);
if (isNaN(interval) || interval < 100) {
logger.error("Interval must be a number larger than 100ms");
process.exit(1);
}
if (pairs.length === 0 || pairs.some((p) => !p.match(/^[A-Z]+-?[A-Z]+$/))) {
logger.error("Invalid pair format (expected BTC-USD or CNYUSD)");
process.exit(1);
}
if (threshold <= 0 || threshold > 100) {
logger.error("Threshold must be between 0 and 100");
process.exit(1);
}
async function main() {
logger.info("Bot starting...");
logger.info(
`[WATCHING] ${pairs.join(", ")} | Every ${interval}ms | Threshold ${threshold}%`,
);
await initDB();
startServer();
try {
await prefetchRates(pairs);
} catch (err) {
logger.error(err, "Critical failure during cache warming");
process.exit(1);
}
fs.promises.writeFile("/tmp/healthy", "ok");
const handleAlert = async (alertData) => {
await insertIntoDB(alertData);
};
const bots = pairs.map((pair) => {
return new Bot(
{
pair,
interval,
threshold,
},
handleAlert,
);
});
bots.forEach((b) => b.start());
const shutdown = async () => {
logger.info("Shutting down...");
bots.forEach((b) => b.stop());
await new Promise((resolve) => setTimeout(resolve, 1000));
await closePool();
process.exit(0);
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
}
main().catch((err) => {
logger.fatal(err, "Fatal error in main loop");
process.exit(1);
});