import { Injectable, Logger, HttpException, HttpStatus } from '@nestjs/common'; import { Cron, CronExpression } from '@nestjs/schedule'; import axios from 'axios'; import { ConfigService } from '@nestjs/config'; import { OrderService } from './order.service'; import { CreateOrderDto } from 'src/dto/create-order.dto'; @Injectable() export class OrderPollingService { private readonly spotwareApiUrl: string; private readonly apiToken: string; private readonly logger = new Logger(OrderPollingService.name); constructor( private readonly configService: ConfigService, private readonly orderService: OrderService, ) { this.spotwareApiUrl = `${this.configService.get<string>('SPOTWARE_API_URL')}`; this.apiToken = this.configService.get<string>('SPOTWARE_API_TOKEN'); } // Poll Spotware API every minute @Cron(CronExpression.EVERY_MINUTE) async pollPositions() { this.logger.log('Polling for open and closed positions...'); try { const openPositions = await this.fetchOpenPositions(); const closedPositions = await this.fetchClosedPositions(); // Process and push positions data to Xano await this.updateXanoWithPositions(openPositions, closedPositions); } catch (error) { this.logger.error(`Error polling positions: ${error.message}`); } } private async fetchOpenPositions() { try { const response = await axios.get(`${this.spotwareApiUrl}/v2/webserv/openPositions`, { headers: { Authorization: `Bearer ${this.apiToken}` }, params: { token: this.apiToken }, }); this.logger.log('Fetched open positions from Spotware'); console.log('Open Positions Data:', response.data); return this.parseCsvData(response.data); } catch (error) { this.logger.error(`Failed to fetch open positions: ${error.message}`); throw new HttpException('Failed to fetch open positions', HttpStatus.FORBIDDEN); } } private async fetchClosedPositions() { const now = new Date(); const to = now.toISOString(); const from = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString(); // 2-day range try { const response = await axios.get(`${this.spotwareApiUrl}/v2/webserv/closedPositions`, { headers: { Authorization: `Bearer ${this.apiToken}` }, params: { from, to, token: this.apiToken }, }); this.logger.log('Fetched closed positions from Spotware'); console.log('Closed Positions Data:', response.data); return this.parseCsvData(response.data); } catch (error) { this.logger.error(`Failed to fetch closed positions: ${error.message}`); throw new HttpException('Failed to fetch closed positions', HttpStatus.FORBIDDEN); } } // Parse CSV data from Spotware response private parseCsvData(csvData: string): any[] { const rows = csvData.split('\n').slice(1); // Skip header row return rows .filter((row) => row.trim()) .map((row) => { const columns = row.split(','); return { login: columns[0], positionId: columns[1], openTimestamp: columns[2], entryPrice: columns[3], direction: columns[4], volume: columns[5], symbol: columns[6], commission: columns[7], swap: columns[8], bookType: columns[9], stake: columns[10], spreadBetting: columns[11], usedMargin: columns[12], }; }); } private async updateXanoWithPositions(openPositions: any[], closedPositions: any[]) { // Process each open position for (const pos of openPositions) { const openOrderData: CreateOrderDto = this.mapOpenPositionToOrderDto(pos); try { // Log the payload for inspection console.log('Open Position Payload for Xano:', openOrderData); await this.orderService.createOrUpdateOrder(openOrderData); this.logger.log(`Open Position sent to Xano: ${JSON.stringify(openOrderData)}`); } catch (error) { this.logger.error(`Failed to send open position to Xano: ${error.message}`); } } // Process each closed position for (const pos of closedPositions) { const closedOrderData: CreateOrderDto = this.mapClosedPositionToOrderDto(pos); try { // Log the payload for inspection console.log('Closed Position Payload for Xano:', closedOrderData); await this.orderService.createOrUpdateOrder(closedOrderData); this.logger.log(`Closed Position sent to Xano: ${JSON.stringify(closedOrderData)}`); } catch (error) { this.logger.error(`Failed to send closed position to Xano: ${error.message}`); } } } private mapOpenPositionToOrderDto(position: any): CreateOrderDto { return { key: position.positionId.toString(), ticket_id: Number(position.positionId), account: Number(position.login), type: position.direction, symbol: position.symbol, volume: parseFloat(position.volume), // Ensure volume is a number entry_price: parseFloat(position.entryPrice), entry_date: position.openTimestamp, broker: 'Spotware', open_reason: position.bookType || 'AUTO', }; } private mapClosedPositionToOrderDto(position: any): CreateOrderDto { return { key: position.positionId.toString(), ticket_id: Number(position.positionId), account: Number(position.login), type: position.direction, symbol: position.symbol, volume: parseFloat(position.volume), entry_price: parseFloat(position.entryPrice), entry_date: position.openTimestamp, close_price: parseFloat(position.closePrice), close_date: position.closeTimestamp, profit: parseFloat(position.pnl), broker: 'Spotware', open_reason: position.bookType || 'AUTO', close_reason: 'AUTO', }; } }
Preview:
downloadDownload PNG
downloadDownload JPEG
downloadDownload SVG
Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!
Click to optimize width for Twitter