please check if this codes logics is okay to place order,take profit and stop loss in spot and futures trading because i tried testing it on a development enviroment is showing internal error so i will drop the code below. please help me and review it if its correct and where a change is needed in the code.
import { addDoc, collection, doc, getDoc, getDocs, limit, onSnapshot, orderBy, query, setDoc, updateDoc, where } from ‘firebase/firestore’;
import { auth, db } from ‘@/lib/firebase’;
import Hex from ‘crypto-js/enc-hex’;
import WebSocket from ‘ws’;
import { getBinanceKeys } from ‘@/lib/db’;
import hmacSHA256 from ‘crypto-js/hmac-sha256’;
class BinanceService {
constructor(userId) {
this.apiKey = process.env.BINANCE_API_KEY;
this.apiSecret = process.env.BINANCE_API_SECRET;
this.userId = auth.currentUser; // Store userId from authenticated context
this.spotBaseUrl = ‘https://api.binance.com/api/v3’;
this.futuresBaseUrl = process.env.BINANCE_TESTNET === ‘true’
? ‘https://testnet.binancefuture.com/fapi/v1’
: ‘’;
this.wsUrl = process.env.BINANCE_TESTNET === ‘true’
? ‘wss://stream.binancefuture.com:9443/ws’
: ‘’;
this.maxRetries = 3;
this.retryDelay = 1000;
this.rateLimitWindow = 60000;
this.rateLimitCount = 0;
this.rateLimitStart = Date.now();
this.requestWeight = 0;
this.maxRequestWeight = 1200;
this.listenKey = null;
this.wsConnection = null;
this.balanceUpdateCallbacks = new Set();
this.notificationCallbacks = new Set();
this.connectionState = {
isConnected: false,
isConnecting: false,
lastError: null,
retryCount: 0,
lastReconnectAttempt: null,
connectionTimeout: null,
pingInterval: null
};
if (!this.apiKey || !this.apiSecret) {
console.warn('Binance API credentials not configured.');
}
this.initializeConnectionState();
}
initializeConnectionState() {
this.connectionState = {
isConnected: false,
isConnecting: false,
lastError: null,
retryCount: 0,
lastReconnectAttempt: null,
connectionTimeout: null,
pingInterval: null
};
}
async makeRequest(url, options = {}, retryCount = 0) {
try {
await this.checkRateLimits();
const headers = {
'X-MBX-APIKEY': this.apiKey,
'Content-Type': 'application/json',
...options.headers
};
let finalUrl = url;
if (options.method === 'GET' || options.method === 'POST') {
const timestamp = Date.now();
const params = {
...options.params,
timestamp,
recvWindow: 5000
};
const queryString = Object.keys(params)
.sort()
.map(key => `${key}=${encodeURIComponent(params[key])}`)
.join('&');
const signature = this.generateSignature(params);
finalUrl = `${url}?${queryString}&signature=${signature}`;
}
const response = await fetch(finalUrl, {
...options,
headers,
timeout: 10000
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(`Binance API Error ${errorData.code}: ${errorData.msg}`);
}
this.updateRateLimitTracking();
return await response.json();
} catch (error) {
console.error('Network request error:', {
url,
error: error.message,
code: error.code,
retryCount
});
if (this.shouldRetry(error, retryCount)) {
await this.handleRetry(retryCount);
return this.makeRequest(url, options, retryCount + 1);
}
if (error.message.includes('timeout')) {
throw new Error('Request timeout. Please try again.');
}
throw error;
}
}
shouldRetry(error, retryCount) {
const isNetworkError = error.message.includes('network') ||
error.message.includes('timeout') ||
error.response?.status === 429;
return isNetworkError && retryCount < this.maxRetries;
}
async handleRetry(retryCount) {
const delay = this.retryDelay * Math.pow(2, retryCount);
console.warn(`Retrying request (${retryCount + 1}/${this.maxRetries}) after ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
async checkRateLimits() {
const now = Date.now();
if (now - this.rateLimitStart > this.rateLimitWindow) {
this.rateLimitCount = 0;
this.rateLimitStart = now;
this.requestWeight = 0;
}
if (this.requestWeight >= this.maxRequestWeight) {
const waitTime = this.rateLimitWindow - (now - this.rateLimitStart);
console.warn(`Rate limit reached. Waiting ${waitTime}ms...`);
await new Promise(resolve => setTimeout(resolve, waitTime));
this.rateLimitCount = 0;
this.rateLimitStart = Date.now();
this.requestWeight = 0;
}
}
updateRateLimitTracking() {
this.rateLimitCount++;
this.requestWeight += 1;
}
async setLeverage(data) {
try {
const { symbol, leverage, userId } = data;
if (!symbol || !leverage) {
throw new Error('Symbol and leverage are required');
}
const response = await this.makeRequest(`${this.futuresBaseUrl}/leverage`, {
method: 'POST',
params: { symbol, leverage }
});
await this.updateUserSettings(userId, { leverage });
return response;
} catch (error) {
console.error('Error setting leverage:', error);
throw new Error(error.message || 'Failed to set leverage');
}
}
async setMargin(data) {
try {
const { symbol, margin, userId } = data;
if (!symbol || !margin) {
throw new Error('Symbol and margin are required');
}
const response = await this.makeRequest(`${this.futuresBaseUrl}/marginType`, {
method: 'POST',
params: { symbol, marginType: margin.toUpperCase() }
});
await this.updateUserSettings(userId, { margin });
return response;
} catch (error) {
console.error('Error setting margin:', error);
throw new Error(error.message || 'Failed to set margin');
}
}
async openPosition(data) {
try {
const { symbol, entryPrice, qPrecision, userId, leverage = 1, positionSide = 'BOTH' } = data;
if (!symbol || !entryPrice || !qPrecision) {
throw new Error('Missing required position parameters');
}
const amount = parseFloat(process.env.BINANCE_AMOUNT || '100') * leverage;
const quantity = amount / entryPrice;
const formattedQuantity = quantity.toFixed(qPrecision);
const response = await this.makeRequest(`${this.futuresBaseUrl}/order`, {
method: 'POST',
params: {
symbol,
side: 'BUY',
type: 'MARKET',
quantity: formattedQuantity,
positionSide
}
});
await this.storeTradeDetails(userId, {
...data,
orderId: response.orderId,
status: 'OPEN',
entryTime: Date.now()
});
return response;
} catch (error) {
console.error('Error opening position:', error);
throw new Error(error.message || 'Failed to open position');
}
}
async setTakeProfit(data, quantity) {
try {
const { symbol, takeProfit, qPrecision, positionSide = 'BOTH', entryPrice } = data;
const formattedQuantity = quantity.toFixed(qPrecision);
const response = await this.makeRequest(`${this.futuresBaseUrl}/order`, {
method: 'POST',
params: {
symbol,
side: 'SELL',
quantity: formattedQuantity,
type: 'TAKE_PROFIT_MARKET',
stopPrice: takeProfit,
positionSide
}
});
const baseAsset = symbol.split('USDT')[0];
const profitAmount = (takeProfit - entryPrice) * quantity;
const clearTime = new Date(Date.now() + 24 * 60 * 60 * 1000);
await this.updateBalanceInFirestore(baseAsset, profitAmount, clearTime);
return response;
} catch (error) {
console.error('Error setting take profit:', error);
throw new Error(error.message || 'Failed to set take profit');
}
}
async getCoinBalance(coin) {
try {
if (!this.apiKey || !this.apiSecret) {
console.warn('Binance API credentials not configured');
return 0;
}
if (!coin) {
console.warn('No coin specified for balance check');
return 0;
}
const response = await this.makeRequest(`${this.spotBaseUrl}/account`, {
method: 'GET'
});
const balance = response.balances.find(b => b.asset === coin);
return balance ? parseFloat(balance.free || '0') + parseFloat(balance.locked || '0') : 0;
} catch (error) {
console.error('Error fetching coin balance:', error);
return 0;
}
}
}
// Export the BinanceService class as default
export default BinanceService;
here the spot processing code:
import { authOptions } from ‘@/lib/auth’;
import binanceService from ‘./binanceService’;
import { createTradeNotification } from ‘@/lib/notificationService’;
import { getAuth } from ‘firebase-admin/auth’;
import { getServerSession } from ‘next-auth’;
export default async function handler(req, res) {
const session = await getServerSession(req, res, authOptions);
if (!session) {
return res.status(401).json({ error: 'Unauthorized' });
}
try {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const { symbol, entryPrice, takeProfit, stopLoss, userId } = req.body;
// Fetch and validate precision
const { quantityPrecision, pricePrecision } = await binanceService.getSymbolInfo(symbol);
if (parseInt(quantityPrecision) !== quantityPrecision || parseInt(pricePrecision) !== pricePrecision) {
return res.status(400).json({ message: 'Invalid quantity or price precision' });
}
const order = await binanceService.placeSpotOrder(req.body);
if (!order) {
return res.status(400).json({ message: 'Failed to place spot order' });
}
// After successful trade execution, create notification
await createTradeNotification(userId, {
type: 'trade',
title: 'New Trade Opened',
message: `Opened ${symbol} trade at ${entryPrice}`,
tradeId: 'trade-id-here', // Replace with actual trade ID
profit: null,
profitPercentage: null
});
// If take profit or stop loss is hit, create another notification
if (takeProfit || stopLoss) {
const price = takeProfit || stopLoss;
const type = takeProfit ? 'take_profit' : 'stop_loss';
const profit = type === 'take_profit' ? price - entryPrice : entryPrice - price;
const profitPercentage = (profit / entryPrice) * 100;
await createTradeNotification(userId, {
type: 'trade_closed',
title: `Trade ${type === 'take_profit' ? 'Take Profit' : 'Stop Loss'} Hit`,
message: `${symbol} trade closed at ${price}`,
tradeId: 'trade-id-here', // Replace with actual trade ID
profit,
profitPercentage
});
}
return res.status(200).json({
message: 'Spot trade processed successfully',
order
});
} catch (error) {
console.error('Error processing spot trade:', error);
return res.status(500).json({ error: 'Internal server error' });
}
}
here is the futures processing codes:
import binanceService from ‘./binanceService’;
import { getAuth } from ‘firebase-admin/auth’;
import telegramService from ‘./telegramService’;
export default async function handler(req, res) {
if (req.method !== ‘POST’) {
return res.status(405).json({ message: ‘Method not allowed’ });
}
try {
// Verify Firebase Authentication
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ message: 'Unauthorized' });
}
const token = authHeader.split('Bearer ')[1];
const decodedToken = await getAuth().verifyIdToken(token);
const userId = decodedToken.uid;
const data = req.body;
data.userId = userId;
// Fetch and validate precision
const { quantityPrecision, pricePrecision } = await binanceService.getSymbolInfo(data.symbol);
if (parseInt(data.qPrecision) !== quantityPrecision || parseInt(data.pPrecision) !== pricePrecision) {
return res.status(400).json({ message: 'Invalid quantity or price precision' });
}
const leverageResult = await binanceService.setLeverage(data);
if (!leverageResult) {
return res.status(400).json({ message: 'Failed to set leverage' });
}
await binanceService.setMargin(data);
const order = await binanceService.openPosition(data);
if (!order) {
return res.status(400).json({ message: 'Failed to open position' });
}
const quantity = order.origQty;
await binanceService.setTakeProfit(data, quantity);
// Send to Telegram
await telegramService.sendMessage(data);
return res.status(200).json({
message: 'Future trade processed successfully',
order
});
} catch (error) {
console.error('Error processing future trade:', error);
return res.status(500).json({
message: 'Internal server error',
error: error.message
});
}
}