backtest work

This commit is contained in:
Boki 2025-07-03 11:04:33 -04:00
parent 143e2e1678
commit 55b4ca78c9
6 changed files with 427 additions and 129 deletions

View file

@ -232,10 +232,12 @@ export class BacktestEngine extends EventEmitter {
},
// Chart data (frontend-ready format)
equity: this.equityCurve.map(point => ({
date: new Date(point.timestamp).toISOString(),
value: point.value
})),
equity: this.equityCurve
.sort((a, b) => a.timestamp - b.timestamp)
.map(point => ({
date: new Date(point.timestamp).toISOString(),
value: point.value
})),
// OHLC data for charts
ohlcData: this.getOHLCData(marketData, validatedConfig.symbols),
@ -375,19 +377,34 @@ export class BacktestEngine extends EventEmitter {
let price = 100; // Base price
let currentTime = startTime;
let trend = 0; // Current trend direction
let trendStrength = 0;
let trendDuration = 0;
while (currentTime <= endTime) {
// Generate random price movement
const changePercent = (Math.random() - 0.5) * 0.04; // +/- 2% daily
// Every 20-50 days, change trend
if (trendDuration <= 0) {
trend = Math.random() > 0.5 ? 1 : -1;
trendStrength = 0.002 + Math.random() * 0.003; // 0.2% to 0.5% daily trend
trendDuration = Math.floor(20 + Math.random() * 30);
}
// Generate price movement with trend and noise
const trendComponent = trend * trendStrength;
const noiseComponent = (Math.random() - 0.5) * 0.03; // +/- 1.5% noise
const changePercent = trendComponent + noiseComponent;
price = price * (1 + changePercent);
// Generate OHLC
const open = price;
const high = price * (1 + Math.random() * 0.02);
const low = price * (1 - Math.random() * 0.02);
const close = price * (1 + (Math.random() - 0.5) * 0.01);
// Generate OHLC with realistic intraday movement
const open = price * (1 + (Math.random() - 0.5) * 0.005);
const dayRange = 0.01 + Math.random() * 0.02; // 1-3% daily range
const high = Math.max(open, price) * (1 + Math.random() * dayRange);
const low = Math.min(open, price) * (1 - Math.random() * dayRange);
const close = low + Math.random() * (high - low);
const volume = Math.random() * 1000000 + 500000;
trendDuration--;
data.push({
type: 'bar',
data: {
@ -572,28 +589,29 @@ export class BacktestEngine extends EventEmitter {
this.container.logger.debug('Fill processed:', fillResult);
}
// Record trade
const trade = {
symbol: fill.symbol,
side: fill.side,
quantity: fill.quantity,
entryPrice: fill.price,
entryTime: fill.timestamp || this.currentTime,
exitPrice: null,
exitTime: null,
pnl: 0,
returnPct: 0,
commission: fill.commission || 0,
currentPrice: fill.price,
holdingPeriod: 0,
backtestTime: this.currentTime
};
this.trades.push(trade);
this.container.logger.info(`💵 Trade recorded: ${fill.side} ${fill.quantity} ${fill.symbol} @ ${fill.price}`);
// Update existing trades if this is a closing trade
if (fill.side === 'sell') {
// Handle trade recording based on side
if (fill.side === 'buy') {
// Create new trade entry for buy orders
const trade = {
symbol: fill.symbol,
side: fill.side,
quantity: fill.quantity,
entryPrice: fill.price,
entryTime: fill.timestamp || this.currentTime,
exitPrice: null,
exitTime: null,
pnl: 0,
returnPct: 0,
commission: fill.commission || 0,
currentPrice: fill.price,
holdingPeriod: 0,
backtestTime: this.currentTime
};
this.trades.push(trade);
this.container.logger.info(`💵 Buy trade opened: ${fill.quantity} ${fill.symbol} @ ${fill.price}`);
} else if (fill.side === 'sell') {
// Update existing trades for sell orders
this.updateClosedTrades(fill);
}
@ -994,7 +1012,8 @@ export class BacktestEngine extends EventEmitter {
low: d.data.low,
close: d.data.close,
volume: d.data.volume
}));
}))
.sort((a, b) => a.time - b.time); // Ensure data is sorted by time
ohlcData[symbol] = symbolData;
});
@ -1066,11 +1085,34 @@ export class BacktestEngine extends EventEmitter {
const tradeToClose = openTrades[0];
tradeToClose.exitPrice = fill.price;
tradeToClose.exitTime = fill.timestamp || this.currentTime;
tradeToClose.pnl = (tradeToClose.exitPrice - tradeToClose.entryPrice) * tradeToClose.quantity - tradeToClose.commission - fill.commission;
tradeToClose.pnl = (tradeToClose.exitPrice - tradeToClose.entryPrice) * tradeToClose.quantity - tradeToClose.commission - (fill.commission || 0);
tradeToClose.returnPct = ((tradeToClose.exitPrice - tradeToClose.entryPrice) / tradeToClose.entryPrice) * 100;
tradeToClose.holdingPeriod = tradeToClose.exitTime - tradeToClose.entryTime;
this.container.logger.info(`💰 Trade closed: P&L ${tradeToClose.pnl.toFixed(2)}, Return ${tradeToClose.returnPct.toFixed(2)}%`);
} else {
// Log if we're trying to sell without an open position
this.container.logger.warn(`⚠️ Sell order for ${fill.symbol} but no open trades found`);
// Still record it as a trade for tracking purposes (short position)
const trade = {
symbol: fill.symbol,
side: 'sell',
quantity: fill.quantity,
entryPrice: fill.price,
entryTime: fill.timestamp || this.currentTime,
exitPrice: null,
exitTime: null,
pnl: 0,
returnPct: 0,
commission: fill.commission || 0,
currentPrice: fill.price,
holdingPeriod: 0,
backtestTime: this.currentTime
};
this.trades.push(trade);
this.container.logger.info(`💵 Short trade opened: ${fill.quantity} ${fill.symbol} @ ${fill.price}`);
}
}
}