backtest work
This commit is contained in:
parent
143e2e1678
commit
55b4ca78c9
6 changed files with 427 additions and 129 deletions
|
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue