Bot & Automation

Treding Forex Ai

/root/hermes-projects/Treding Forex Ai

dist/src/services/tradingService.js text
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TradingService = void 0;
exports.calcPnl = calcPnl;
const risk_1 = require("../constants/risk");
const markets_1 = require("../constants/markets");
class TradingService {
    strategy;
    riskManager;
    store;
    broker;
    constructor(strategy, riskManager, store, broker) {
        this.strategy = strategy;
        this.riskManager = riskManager;
        this.store = store;
        this.broker = broker;
    }
    async createSignal(params) {
        const symbol = (0, markets_1.findMarketSymbol)(params.symbolCode);
        if (!symbol) {
            throw new Error("Symbol market tidak didukung.");
        }
        const signal = await this.strategy.analyze(symbol, params.mode ?? risk_1.DEFAULT_TRADING_MODE);
        const openTrades = await this.store.listOpenPaperTrades(params.chatId);
        const todayTrades = await this.store.listTodayPaperTrades(params.chatId);
        const todayLoss = estimateTodayLoss(todayTrades);
        const openTradeCount = await this.resolveOpenTradeCount(params.chatId, openTrades.length);
        const riskDecision = this.riskManager.evaluate(signal, openTradeCount, todayLoss);
        if (!riskDecision.accepted || !riskDecision.positionSizing) {
            return {
                signal,
                orderIntent: null,
                riskNotes: riskDecision.reasons
            };
        }
        const orderIntent = this.riskManager.buildOrderIntent({
            chatId: params.chatId,
            userId: params.userId,
            signal,
            positionSizing: riskDecision.positionSizing
        });
        await this.store.savePendingOrder(orderIntent);
        return {
            signal,
            orderIntent,
            riskNotes: riskDecision.reasons
        };
    }
    async executePendingOrder(orderId, options) {
        const order = await this.store.getPendingOrder(orderId);
        if (!order) {
            throw new Error("Order preview sudah tidak tersedia.");
        }
        const trade = await this.broker.execute(order, options);
        if (trade.broker !== "paper") {
            await this.store.appendPaperTrade(trade);
            await this.store.removePendingOrder(order.id);
        }
        return trade;
    }
    async getBrokerExecutionStatus() {
        return this.broker.getExecutionStatus();
    }
    async resolveOpenTradeCount(chatId, localOpenTradeCount) {
        const brokerStatus = await this.broker.getExecutionStatus().catch(() => null);
        if (!brokerStatus || brokerStatus.provider !== "mt5_bridge" || !brokerStatus.connected) {
            return localOpenTradeCount;
        }
        const positions = await this.broker.getPositions().catch(() => null);
        if (!positions) {
            return localOpenTradeCount;
        }
        const localPaperTrades = await this.store.listOpenPaperTrades(chatId);
        const localPaperOnlyCount = localPaperTrades.filter((trade) => trade.broker === "paper").length;
        return localPaperOnlyCount + positions.length;
    }
    /**
     * Tutup posisi paper trade berdasarkan orderId.
     * P&L dihitung dari selisih harga entry vs closePrice.
     */
    async closePaperPosition(orderId, closePrice) {
        const allOpen = await this.store.listAllOpenTrades();
        const trade = allOpen.find((t) => t.id === orderId);
        if (!trade) {
            return null;
        }
        const pnl = calcPnl(trade, closePrice);
        return this.store.closeTrade(orderId, closePrice, pnl);
    }
    async addToWatchlist(chatId, symbolCode) {
        const symbol = (0, markets_1.findMarketSymbol)(symbolCode);
        if (!symbol) {
            throw new Error("Symbol market tidak didukung.");
        }
        await this.store.addWatchlist(chatId, symbol.code);
    }
    async getWatchlist(chatId) {
        const entries = await this.store.listWatchlist(chatId);
        return entries.map((entry) => entry.symbolCode);
    }
    async getTodayLoss(chatId) {
        const todayTrades = await this.store.listTodayPaperTrades(chatId);
        return estimateTodayLoss(todayTrades);
    }
}
exports.TradingService = TradingService;
function estimateTodayLoss(trades) {
    return trades.reduce((total, trade) => {
        const pnl = trade.pnl ?? 0;
        return pnl < 0 ? total + Math.abs(pnl) : total;
    }, 0);
}
/**
 * Hitung approximate P&L dalam satuan USD.
 * Untuk akurasi penuh seharusnya pakai symbol.pipValuePerLot,
 * namun di sini digunakan perkiraan: (pips * lotSize * 10_000 / 1000).
 * XAUUSD: 1 pip = $0.1 per 0.01 lot → factor 10 sudah representatif untuk paper mode.
 */
function calcPnl(trade, closePrice) {
    const priceDiff = trade.action === "BUY" ? closePrice - trade.entry : trade.entry - closePrice;
    const rawPnl = priceDiff * trade.positionSizing.lotSize * 100;
    return Math.round(rawPnl * 100) / 100;
}