work on backtest

This commit is contained in:
Boki 2025-07-03 09:55:13 -04:00
parent 5a3a23a2ba
commit 143e2e1678
9 changed files with 613 additions and 46 deletions

View file

@ -167,7 +167,21 @@ export class BacktestEngine extends EventEmitter {
this.container.logger.info(`Loaded ${marketData.length} market data points`);
// Initialize strategies
await this.strategyManager.initializeStrategies(validatedConfig.strategies || []);
const strategies = validatedConfig.strategies || [{
id: `${validatedConfig.strategy}-${Date.now()}`,
name: validatedConfig.strategy,
enabled: true,
parameters: {
symbols: validatedConfig.symbols,
initialCapital: validatedConfig.initialCapital,
// Add any default strategy parameters here
},
symbols: validatedConfig.symbols,
allocation: 1.0 // Use 100% of capital
}];
await this.strategyManager.initializeStrategies(strategies);
this.container.logger.info(`Initialized ${strategies.length} strategies`);
// Convert market data to events
this.populateEventQueue(marketData);
@ -421,6 +435,12 @@ export class BacktestEngine extends EventEmitter {
this.currentTime = event.timestamp;
if (tradingEngine) {
await tradingEngine.advanceTime(this.currentTime);
// Check for any fills
const fills = tradingEngine.getFills ? tradingEngine.getFills() : [];
for (const fill of fills) {
await this.processFill(fill);
}
}
// Process event based on type
@ -523,6 +543,9 @@ export class BacktestEngine extends EventEmitter {
// Let strategies process the data
await this.strategyManager.onMarketData(data);
// Check for any pending orders that should be filled
await this.checkAndFillOrders(data);
// Track performance
this.performanceAnalyzer.addEquityPoint(
new Date(this.currentTime),
@ -536,11 +559,55 @@ export class BacktestEngine extends EventEmitter {
}
private async processFill(fill: any): Promise<void> {
// Process fill in trading engine for position tracking
const tradingEngine = this.strategyManager.getTradingEngine();
if (tradingEngine) {
const fillResult = await tradingEngine.processFill(
fill.symbol,
fill.price,
fill.quantity,
fill.side,
fill.commission || 0
);
this.container.logger.debug('Fill processed:', fillResult);
}
// Record trade
this.trades.push({
...fill,
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') {
this.updateClosedTrades(fill);
}
// Notify strategies of fill
const strategy = fill.strategyId ? this.strategyManager.getStrategy(fill.strategyId) : null;
if (strategy) {
await strategy.onOrderUpdate({
orderId: fill.orderId,
symbol: fill.symbol,
side: fill.side,
status: 'filled',
fills: [fill]
});
}
// Store in database
await this.storageService.storeFill(fill);
@ -934,4 +1001,76 @@ export class BacktestEngine extends EventEmitter {
return ohlcData;
}
private pendingOrders: Map<string, any> = new Map();
private ordersListenerSetup = false;
private async checkAndFillOrders(data: MarketData): Promise<void> {
if (data.type !== 'bar') return;
const symbol = data.data.symbol;
const currentPrice = data.data.close;
// Listen for orders from strategy manager
if (!this.ordersListenerSetup) {
this.strategyManager.on('order', async (orderEvent: any) => {
this.container.logger.info('New order received:', orderEvent);
this.pendingOrders.set(orderEvent.orderId, {
...orderEvent,
timestamp: this.currentTime
});
});
this.ordersListenerSetup = true;
}
// Check pending orders for this symbol
for (const [orderId, orderEvent] of this.pendingOrders) {
if (orderEvent.order.symbol === symbol) {
// For market orders, fill immediately at current price
if (orderEvent.order.orderType === 'market') {
const fillPrice = orderEvent.order.side === 'buy' ?
currentPrice * (1 + (this.marketSimulator.config.slippage || 0.0001)) :
currentPrice * (1 - (this.marketSimulator.config.slippage || 0.0001));
const fill = {
orderId,
symbol: orderEvent.order.symbol,
side: orderEvent.order.side,
quantity: orderEvent.order.quantity,
price: fillPrice,
timestamp: this.currentTime,
commission: orderEvent.order.quantity * fillPrice * 0.001, // 0.1% commission
strategyId: orderEvent.strategyId
};
this.container.logger.info(`✅ Filling ${orderEvent.order.side} order for ${symbol} @ ${fillPrice}`);
await this.processFill(fill);
this.pendingOrders.delete(orderId);
}
// TODO: Handle limit orders
}
}
}
private updateClosedTrades(fill: any): void {
// Find open trades for this symbol
const openTrades = this.trades.filter(t =>
t.symbol === fill.symbol &&
t.side === 'buy' &&
!t.exitTime
);
if (openTrades.length > 0) {
// FIFO - close oldest trade first
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.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)}%`);
}
}
}