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;
}