trade-backend/src/models/Candles.ts
AzizbekFayziyev e62342e016 finish
2025-07-01 21:07:46 +05:00

179 lines
4.9 KiB
TypeScript

import { Op } from 'sequelize';
import Period from '../interfaces/common/Period.js';
import dexModel from './Dex.js';
import Transaction from '../schemes/Transaction.js';
import Order from '../schemes/Order.js';
class CandlesModel {
async getCandles(pairId: string, period: Period) {
try {
const pairRow = await dexModel.getPairRow(parseInt(pairId, 10));
if (!pairRow) return { success: false, data: 'Invalid pair data' };
const buyOrders = await Order.findAll({
where: {
pair_id: pairRow.id,
type: 'buy',
},
});
const buyOrdersIds = buyOrders.map((order) => order.id);
interface TransactionData {
timestamp: string;
amount: string;
id: number;
price: string;
buy_order_id: number;
}
const transactions = (await Transaction.findAll({
where: {
buy_order_id: {
[Op.in]: buyOrdersIds,
},
status: 'confirmed',
},
order: [['timestamp', 'ASC']],
attributes: ['timestamp', 'amount', 'id', 'amount', 'buy_order_id'],
}).then((transactions) =>
transactions
.map((transaction) => transaction.toJSON())
.map((transaction) => ({
...transaction,
price: buyOrders.find((order) => order.id === transaction.buy_order_id)
?.price,
})),
)) as TransactionData[];
const aggregationPeriod = (() => {
switch (period) {
case '1h':
return 3600000;
case '1d':
return 86400000;
case '1w':
return 604800000;
case '1m':
return 2592000000;
default:
return 3600000;
}
})();
interface ResultCandle {
pair_id: number;
timestamp: number;
shadow_top: number;
shadow_bottom: number;
body_first: number;
body_second: number;
}
const foundCandles = transactions.reduce((acc: ResultCandle[], transaction) => {
const currentTimestamp = parseInt(transaction.timestamp, 10);
const lastCadle = acc[acc.length - 1];
if (!lastCadle) {
return [
{
pair_id: pairRow.id,
timestamp: currentTimestamp,
shadow_top: parseFloat(transaction.price),
shadow_bottom: parseFloat(transaction.price),
body_first: parseFloat(transaction.price),
body_second: parseFloat(transaction.price),
},
];
}
if (lastCadle.timestamp + aggregationPeriod < currentTimestamp) {
// creata new candle
const prevCandleEnding = lastCadle.body_second;
return [
...acc,
{
pair_id: pairRow.id,
timestamp: currentTimestamp,
shadow_top: parseFloat(transaction.price),
shadow_bottom: parseFloat(transaction.price),
body_first: prevCandleEnding,
body_second: parseFloat(transaction.price),
},
];
}
// add to existing candle
const newCandle = {
...lastCadle,
shadow_top: Math.max(lastCadle.shadow_top, parseFloat(transaction.price)),
shadow_bottom: Math.min(lastCadle.shadow_bottom, parseFloat(transaction.price)),
body_second: parseFloat(transaction.price),
};
acc[acc.length - 1] = newCandle;
return acc;
}, [] as ResultCandle[]);
const endTimestamp = Date.now();
const completeCandles = [];
let currentTimestamp =
foundCandles[0]?.timestamp || endTimestamp - aggregationPeriod * 10;
let lastRealCandle = {
pair_id: pairRow.id,
timestamp: currentTimestamp,
shadow_top: foundCandles[0]?.body_second || 0,
shadow_bottom: foundCandles[0]?.body_second || 0,
body_first: foundCandles[0]?.body_second || 0,
body_second: foundCandles[0]?.body_second || 0,
};
for (let i = 0; i < foundCandles.length; i++) {
const candle = foundCandles[i];
// Fill gaps with "empty" candles that replicate the last real candle's values
while (currentTimestamp < candle.timestamp) {
completeCandles.push({
pair_id: pairRow.id,
timestamp: currentTimestamp,
shadow_top: lastRealCandle.body_second,
shadow_bottom: lastRealCandle.body_second,
body_first: lastRealCandle.body_second,
body_second: lastRealCandle.body_second,
});
currentTimestamp += aggregationPeriod;
}
// Add the actual candle
completeCandles.push(candle);
lastRealCandle = candle; // Update last real candle to the current one
currentTimestamp = candle.timestamp + aggregationPeriod;
}
// Fill any remaining gaps up to the current time (endTimestamp) with the last known real candle's values
while (currentTimestamp <= endTimestamp) {
completeCandles.push({
pair_id: pairRow.id,
timestamp: currentTimestamp,
shadow_top: lastRealCandle.body_second,
shadow_bottom: lastRealCandle.body_second,
body_first: lastRealCandle.body_second,
body_second: lastRealCandle.body_second,
});
currentTimestamp += aggregationPeriod;
}
return { success: true, data: completeCandles || [] };
} catch (err) {
console.log(err);
return { success: false, data: 'Internal error' };
}
}
}
const candlesModel = new CandlesModel();
export default candlesModel;