work on backtest
This commit is contained in:
parent
5a3a23a2ba
commit
143e2e1678
9 changed files with 613 additions and 46 deletions
|
|
@ -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)}%`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue