This is the Crypto Market Price Game, can you balance the price calculation?
because it is so imbalance between buy and sell

class CryptoMarketSimulator {
    constructor(coin, dataCoin) {
        const { initialPrice, currentPrice, adjustmentRatio, initialSupply, circulatingSupply, maxSupply, totalVolume } = dataCoin;

        this.coin = coin;
        this.initialPrice = initialPrice;
        this.currentPrice = currentPrice || initialPrice;
        this.adjustmentRatio = adjustmentRatio;
        this.circulatingSupply = circulatingSupply || initialSupply;
        this.maxSupply = maxSupply;
        this.marketCap = this.currentPrice * this.circulatingSupply;
        this.totalVolume = totalVolume;
    }

    updatePrice(transaction) {
        const { type, content } = transaction;
        if (type === "buy") {
            this.currentPrice *= (1 + this.adjustmentRatio * content / (this.circulatingSupply + content));
        } else if (type === "sell") {
            this.currentPrice *= (1 - this.adjustmentRatio * content / (this.circulatingSupply - content));
        }
        this.currentPrice = Math.max(1000, this.currentPrice); // Ensure price doesn't go negative
    }

    async updateMetrics(transaction) {
        // this.circulatingSupply = Math.min(this.maxSupply, this.circulatingSupply + transaction.content);
        if(transaction.type === 'buy') {
            this.circulatingSupply += transaction.content;
        } else if(transaction.type === 'sell') {
            this.circulatingSupply -= transaction.content;
        }
        this.marketCap = this.currentPrice * this.circulatingSupply;
        if(!this.totalVolume) this.totalVolume = 0;
        this.totalVolume += transaction.content;
        await _mongo_CommandSchema.updateOne({
            cId: 'invest_price'
        }, {
            $set: {
                [invest.${this.coin}.currentPrice]: this.currentPrice,
                [invest.${this.coin}.circulatingSupply]: this.circulatingSupply,
                [invest.${this.coin}.marketCap]: this.marketCap,
                [invest.${this.coin}.totalVolume]: this.totalVolume
            }
        })
        await _mongo_InvestHistorySchema.create({
            coin: this.coin,
            hash: transaction.hash,
            from: transaction.from,
            to: transaction.to,
            date: transaction.date,
            type: transaction.type,
            content: transaction.content,
            price: this.currentPrice
        })
    }

    async calculateSMA(period) {
        const endDate = new Date();
        const startDate = new Date(endDate.getTime() - period);
        
        const filteredPriceHistory = await _mongo_InvestHistorySchema.find({
            coin: this.coin,
            date: { $gte: startDate, $lte: endDate }
        }).sort({ date: -1 });

        if (filteredPriceHistory.length === 0) {
            return null; // or throw an error
        }

        const sum = filteredPriceHistory.reduce((a, b) => a + b.price, 0);
        return sum / filteredPriceHistory.length;
    }

    async calculateVolatility(period) {
        const endDate = new Date();
        const startDate = new Date(endDate.getTime() - period);
        
        const filteredPriceHistory = await _mongo_InvestHistorySchema.find({
            coin: this.coin,
            date: { $gte: startDate, $lte: endDate }
        }).sort({ date: 1 }); // Sort in ascending order

        if (filteredPriceHistory.length < 2) {
            return null; // Not enough data to calculate volatility
        }

        const priceHistory = filteredPriceHistory.map(data => data.price);
        const returns = priceHistory.slice(1).map((price, index) => Math.log(price / priceHistory[index]));
        const meanReturn = returns.reduce((sum, return_) => sum + return_, 0) / returns.length;
        const squaredDeviations = returns.map(return_ => Math.pow(return_ - meanReturn, 2));
        const variance = squaredDeviations.reduce((sum, deviation) => sum + deviation, 0) / (returns.length - 1);
        const volatility = Math.sqrt(variance * 252); // Annualized volatility assuming 252 trading days

        return volatility;
    }

    async calculateWAP(period = null) {
        let matchStage = { $match: { coin: this.coin } };

        if (period) {
            const endDate = new Date();
            const startDate = new Date(endDate.getTime() - period);
            matchStage.$match.date = { $gte: startDate, $lte: endDate };
        }

        const result = await _mongo_InvestHistorySchema.aggregate([
            matchStage,
            {
                $group: {
                    _id: null,
                    totalValue: { $sum: { $multiply: ["$price", "$content"] } },
                    totalVolume: { $sum: "$content" }
                }
            }
        ]);

        if (result.length > 0) {
            const { totalValue, totalVolume } = result[0];
            return totalVolume > 0 ? totalValue / totalVolume : null;
        }

        return null;
    }

    async calculateOHLC(intervalMinutes = 5, totalHours = 3) {
        /**
         * This function calculates the OHLC values based on the given interval and total hours.
         * 
         * Parameters:
         * intervalMinutes (int): The time interval in minutes to calculate OHLC values.
         * totalHours (int): The total number of hours for which OHLC values need to be calculated.
         * priceData (Array): An array of objects, each containing 'date' (JavaScript Date object) and 'price'.
         * 
         * Returns:
         * Array: An array of objects with each object containing x (date), o (open price), h (high price),
         *        l (low price), and c (close price).
         */
        // Step 1: Initialize an empty array to hold OHLC values
        let ohlcValues = [];

        const startTimeMongoFind = Date.now() - (totalHours * 60 * 60 * 1000);
        const priceHistory = await _mongo_InvestHistorySchema.find({
            coin: this.coin,
            date: { $gt: startTimeMongoFind }
        }).sort({ date: 1 });
        const priceData = priceHistory.map(data => ({ date: new Date(data.date), price: data.price.toFixed(2) }));
        if(priceData.length === 0) {
            return null;
        }
        
        // Step 2: Calculate number of intervals
        let numIntervals = Math.floor((totalHours * 60) / intervalMinutes);
    
        // Step 3: Create a Date object to track intervals
        let startTime = priceData[0].date;
    
        for (let i = 0; i < numIntervals; i++) {
            // Calculate the start and end times for this interval
            let intervalStart = new Date(startTime.getTime() + i * intervalMinutes * 60000);
            let intervalEnd = new Date(intervalStart.getTime() + intervalMinutes * 60000);
    
            // Filter the price data to include only data points within the current interval
            let intervalData = priceData.filter(data => data.date >= intervalStart && data.date < intervalEnd);
    
            if (intervalData.length === 0) continue;
    
            let openPrice = intervalData[0].price;
            let highPrice = Math.max(...intervalData.map(data => data.price));
            let lowPrice = Math.min(...intervalData.map(data => data.price));
            let closePrice = intervalData[intervalData.length - 1].price;
    
            let ohlc = {
                x: intervalStart,
                o: Number(openPrice),
                h: Number(highPrice),
                l: Number(lowPrice),
                c: Number(closePrice)
            };
    
            ohlcValues.push(ohlc);
        }
    
        return ohlcValues;
    }
    
    
    

    // New method to generate candlestick chart
    async generateCandlestickChart() {
        let ohlcData = await this.calculateOHLC();
        if (!ohlcData) {
            // console.log('Not enough data to generate chart');
            // return null;
            ohlcData = [{ x: new Date(), o: this.currentPrice, h: this.currentPrice, l: this.currentPrice, c: this.currentPrice }];
        }
    
        // Convert OHLC data to Chart.js format
        const formattedData = ohlcData.map(({ x, o, h, l, c }, index) => ({
            x: moment(x).valueOf(),
            o,
            h,
            l,
            c,
        }));
        console.log('Formatted data:', formattedData);
    
        const width = 1200;  // Define the width of the chart
        const height = 800;  // Define the height of the chart
    
        // Use the correct settings for Chart.js canvas creation
        const chartJSNodeCanvas = new ChartJSNodeCanvas({
            width,
            height,
            plugins: {
                modern: ['chartjs-chart-financial'],
                globalVariableLegacy: ['chartjs-adapter-luxon']
            },
            chartCallback: () => {
                global.window = global.window || {};
                global.window.luxon = require('luxon');
            }
        });
    
        // const configuration = {
        //     type: 'candlestick',
        //     data: {
        //         datasets: [{
        //             label: ${this.coin} Price History,
        //             data: formattedData
        //         }]
        //     },
        //     options: {
        //         plugins: {
        //             legend: {
        //                 display: false,
        //             }
        //         },
        //         scales: {
        //             x: {
        //                 type: 'time',
        //                 time: {
        //                     unit: 'minute',
        //                     tooltipFormat: 'HH:mm'
        //                 }
        //             },
        //         }
        //     }
        // };
        const configuration = {
            type: 'candlestick',
            data: {
                datasets: [{
                    label: ${this.coin} Price History,
                    data: formattedData
                }]
            },
            options: {
                plugins: {
                    legend: {
                        display: false,
                    }
                },
                scales: {
                    x: {
                        type: 'time',
                        time: {
                            unit: 'minute',
                            tooltipFormat: 'HH:mm'
                        }
                    },
                    y: {
                        beginAtZero: false,
                        grace: '5%',
                        ticks: {
                            maxTicksLimit: 5
                        }
                    }
                }
            }
        };
    
        try {
            const imageBuffer = await chartJSNodeCanvas.renderToBuffer(configuration);
            return imageBuffer;
        } catch (error) {
            console.error('Error generating candlestick chart:', error);
            return null;
        }
    }

    // example of transaction
    // const transaction = {
    //     hash: 'XXX',
    //     from: 'XXX',
    //     to: 'XXX',
    //     date: Date.now(),
    //     type: 'buy',
    //     content: 100,
    // }
    async processTransaction(transaction) {
        this.updatePrice(transaction);
        await this.updateMetrics(transaction);

        return {
            currentPrice: this.currentPrice,
            marketCap: this.marketCap,
            circulatingSupply: this.circulatingSupply,
            totalVolume: this.totalVolume
        };
    }
}