179 lines
4.9 KiB
TypeScript
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;
|