fixed test strat

This commit is contained in:
Boki 2025-07-03 18:33:15 -04:00
parent 6cf3179092
commit 8a9a4bc336
12 changed files with 1301 additions and 8 deletions

Binary file not shown.

View file

@ -110,10 +110,65 @@ impl PositionTracker {
// Handle trade matching for closed trades
match side {
Side::Buy => {
// For buy orders, just add to open trades
self.open_trades.entry(symbol.to_string())
.or_insert_with(Vec::new)
.push(trade_record);
// For buy orders, try to match with open sell trades (closing shorts)
if let Some(mut open_trades) = self.open_trades.get_mut(symbol) {
let mut remaining_quantity = fill.quantity;
let mut trades_to_remove = Vec::new();
// FIFO matching against short positions
for (idx, open_trade) in open_trades.iter_mut().enumerate() {
if open_trade.side == Side::Sell && remaining_quantity > 0.0 {
let close_quantity = remaining_quantity.min(open_trade.quantity);
// Create closed trade record for short position
let closed_trade = ClosedTrade {
id: format!("CT{}", self.generate_trade_id()),
symbol: symbol.to_string(),
entry_time: open_trade.timestamp,
exit_time: fill.timestamp,
entry_price: open_trade.price,
exit_price: fill.price,
quantity: close_quantity,
side: Side::Sell, // Opening side (short)
pnl: close_quantity * (open_trade.price - fill.price) - (open_trade.commission + fill.commission * close_quantity / fill.quantity),
pnl_percent: ((open_trade.price - fill.price) / open_trade.price) * 100.0,
commission: open_trade.commission + fill.commission * close_quantity / fill.quantity,
duration_ms: (fill.timestamp - open_trade.timestamp).num_milliseconds(),
entry_fill_id: open_trade.id.clone(),
exit_fill_id: trade_record.id.clone(),
};
self.closed_trades.write().push(closed_trade);
// Update quantities
remaining_quantity -= close_quantity;
open_trade.quantity -= close_quantity;
if open_trade.quantity <= 0.0 {
trades_to_remove.push(idx);
}
}
}
// Remove fully closed trades
for idx in trades_to_remove.into_iter().rev() {
open_trades.remove(idx);
}
// If we still have quantity left, it's a new long position
if remaining_quantity > 0.0 {
let long_trade = TradeRecord {
quantity: remaining_quantity,
..trade_record.clone()
};
open_trades.push(long_trade);
}
} else {
// No open trades, start a new long position
self.open_trades.entry(symbol.to_string())
.or_insert_with(Vec::new)
.push(trade_record);
}
}
Side::Sell => {
// For sell orders, try to match with open buy trades
@ -169,6 +224,11 @@ impl PositionTracker {
};
open_trades.push(short_trade);
}
} else {
// No open trades, start a new short position
self.open_trades.entry(symbol.to_string())
.or_insert_with(Vec::new)
.push(trade_record);
}
}
}

View file

@ -0,0 +1,113 @@
import { BacktestEngine } from '../src/backtest/BacktestEngine';
import { StrategyManager } from '../src/strategies/StrategyManager';
import { StorageService } from '../src/services/StorageService';
import { ModeManager } from '../src/core/ModeManager';
import { MarketDataService } from '../src/services/MarketDataService';
import { ExecutionService } from '../src/services/ExecutionService';
import { IServiceContainer } from '@stock-bot/di';
async function debugPositionTracking() {
// Create service container with detailed logging
const container: IServiceContainer = {
logger: {
info: (msg: string, ...args: any[]) => console.log('[INFO]', msg, ...args),
error: (msg: string, ...args: any[]) => console.error('[ERROR]', msg, ...args),
warn: (msg: string, ...args: any[]) => console.warn('[WARN]', msg, ...args),
debug: (msg: string, ...args: any[]) => console.log('[DEBUG]', msg, ...args),
} as any,
custom: {}
};
// Initialize services
const storageService = new StorageService();
const marketDataService = new MarketDataService(container);
const executionService = new ExecutionService(container);
const modeManager = new ModeManager(container, marketDataService, executionService, storageService);
const strategyManager = new StrategyManager(container);
// Set services in container
container.custom = {
MarketDataService: marketDataService,
ExecutionService: executionService,
ModeManager: modeManager,
StorageService: storageService
};
// Initialize backtest mode with full config
await modeManager.initializeMode({
mode: 'backtest',
startDate: '2023-06-01T00:00:00Z',
endDate: '2023-09-01T00:00:00Z', // Just 3 months for debugging
speed: 'max',
symbols: ['AAPL'],
initialCapital: 100000,
dataFrequency: '1d',
strategy: 'sma-crossover'
});
// Create backtest engine
const backtestEngine = new BacktestEngine(container, storageService, strategyManager);
// Listen to strategy events for debugging
strategyManager.on('order', (orderEvent: any) => {
console.log('\n📊 STRATEGY ORDER EVENT:', orderEvent);
});
// Configure backtest
const config = {
mode: 'backtest' as const,
name: 'Position Tracking Debug',
strategy: 'sma-crossover',
symbols: ['AAPL'],
startDate: '2023-06-01T00:00:00Z',
endDate: '2023-09-01T00:00:00Z',
initialCapital: 100000,
commission: 0.001,
slippage: 0.0001,
dataFrequency: '1d' as const,
speed: 'max' as const
};
console.log('Starting position tracking debug...\n');
try {
const result = await backtestEngine.runBacktest(config);
console.log('\n=== FINAL RESULTS ===');
console.log(`Total Trades: ${result.metrics.totalTrades}`);
console.log(`Win Rate: ${result.metrics.winRate.toFixed(2)}%`);
console.log('\n=== TRADE DETAILS ===');
let buyCount = 0;
let sellCount = 0;
result.trades.forEach((trade, i) => {
console.log(`\nTrade ${i + 1}:`);
console.log(` Symbol: ${trade.symbol}`);
console.log(` Side: ${trade.side}`);
console.log(` Entry: ${trade.entryDate} @ $${trade.entryPrice.toFixed(2)}`);
console.log(` Exit: ${trade.exitDate} @ $${trade.exitPrice.toFixed(2)}`);
console.log(` Quantity: ${trade.quantity}`);
console.log(` P&L: $${trade.pnl.toFixed(2)}`);
if (trade.side === 'buy') buyCount++;
else if (trade.side === 'sell') sellCount++;
});
console.log(`\n=== TRADE SIDE SUMMARY ===`);
console.log(`Buy trades: ${buyCount}`);
console.log(`Sell trades: ${sellCount}`);
// Check final positions
console.log('\n=== FINAL POSITIONS ===');
result.positions.forEach(pos => {
console.log(`${pos.symbol}: ${pos.quantity} shares @ avg $${pos.averagePrice.toFixed(2)}`);
});
} catch (error) {
console.error('Debug failed:', error);
}
}
// Run the debug
debugPositionTracking().catch(console.error);

View file

@ -0,0 +1,129 @@
import { TradingEngine } from '@stock-bot/core';
async function debugRustShortTrades() {
// Create engine config for backtest mode
const engineConfig = {
startTime: new Date('2023-01-01').getTime(),
endTime: new Date('2023-02-01').getTime(),
speedMultiplier: 0
};
console.log('=== RUST CORE SHORT TRADE DEBUG ===\n');
console.log('1. Creating TradingEngine in backtest mode...');
const engine = new TradingEngine('backtest', engineConfig);
// Simulate opening a short position
console.log('\n2. Submitting SELL order to open short position...');
const sellOrder = {
id: 'sell-order-001',
symbol: 'TEST',
side: 'sell',
quantity: 100,
orderType: 'market',
timeInForce: 'DAY'
};
const sellOrderId = engine.submitOrder(sellOrder);
console.log(` Sell order ID: ${sellOrderId}`);
// Process the sell fill
console.log('\n3. Processing SELL fill...');
const sellFillResult = engine.processFillWithMetadata(
'TEST', // symbol
50.0, // price
100, // quantity
'sell', // side
0.1, // commission
sellOrderId, // orderId
'debug-strategy' // strategyId
);
console.log(` Fill result: ${sellFillResult}`);
// Check positions
console.log('\n4. Checking positions after SELL...');
const positionsAfterSell = engine.getPosition('TEST');
console.log(' Position:', positionsAfterSell);
// Check open trades
const openTradesAfterSell = JSON.parse(engine.getOpenTrades());
console.log(' Open trades:', openTradesAfterSell.length);
if (openTradesAfterSell.length > 0) {
console.log(' First open trade:', openTradesAfterSell[0]);
}
// Check closed trades
const closedTradesAfterSell = JSON.parse(engine.getClosedTrades());
console.log(' Closed trades:', closedTradesAfterSell.length);
// Now close the short with a buy order
console.log('\n5. Submitting BUY order to close short position...');
const buyOrder = {
id: 'buy-order-001',
symbol: 'TEST',
side: 'buy',
quantity: 100,
orderType: 'market',
timeInForce: 'DAY'
};
const buyOrderId = engine.submitOrder(buyOrder);
console.log(` Buy order ID: ${buyOrderId}`);
// Process the buy fill
console.log('\n6. Processing BUY fill...');
const buyFillResult = engine.processFillWithMetadata(
'TEST', // symbol
48.0, // price (profit on short)
100, // quantity
'buy', // side
0.1, // commission
buyOrderId, // orderId
'debug-strategy' // strategyId
);
console.log(` Fill result: ${buyFillResult}`);
// Check positions after closing
console.log('\n7. Checking positions after BUY...');
const positionsAfterBuy = engine.getPosition('TEST');
console.log(' Position:', positionsAfterBuy);
// Check open trades after closing
const openTradesAfterBuy = JSON.parse(engine.getOpenTrades());
console.log(' Open trades:', openTradesAfterBuy.length);
// Check closed trades after closing - THIS IS WHERE WE SHOULD SEE THE SHORT TRADE
const closedTradesAfterBuy = JSON.parse(engine.getClosedTrades());
console.log(' Closed trades:', closedTradesAfterBuy.length);
if (closedTradesAfterBuy.length > 0) {
console.log('\n8. Closed trade details:');
closedTradesAfterBuy.forEach((trade: any, i: number) => {
console.log(` Trade ${i + 1}:`);
console.log(` ID: ${trade.id}`);
console.log(` Side: ${trade.side}`);
console.log(` Entry: ${trade.entry_price} @ ${trade.entry_time}`);
console.log(` Exit: ${trade.exit_price} @ ${trade.exit_time}`);
console.log(` P&L: ${trade.pnl} (${trade.pnl_percent}%)`);
console.log(` Quantity: ${trade.quantity}`);
});
} else {
console.log('\n❌ ERROR: No closed trades found! Short trade was not recorded.');
}
// Also check trade history
console.log('\n9. Trade history:');
const tradeHistory = JSON.parse(engine.getTradeHistory());
tradeHistory.forEach((trade: any, i: number) => {
console.log(` Trade ${i + 1}: ${trade.side} ${trade.quantity} @ ${trade.price}`);
});
// Check totals
console.log('\n10. Summary:');
console.log(` Total trades: ${engine.getTradeCount()}`);
console.log(` Closed trades: ${engine.getClosedTradeCount()}`);
const [realizedPnl, unrealizedPnl] = engine.getTotalPnl();
console.log(` Realized P&L: ${realizedPnl}`);
console.log(` Unrealized P&L: ${unrealizedPnl}`);
}
// Run the debug
debugRustShortTrades().catch(console.error);

View file

@ -0,0 +1,161 @@
import { BacktestEngine } from '../src/backtest/BacktestEngine';
import { StrategyManager } from '../src/strategies/StrategyManager';
import { StorageService } from '../src/services/StorageService';
import { ModeManager } from '../src/core/ModeManager';
import { MarketDataService } from '../src/services/MarketDataService';
import { ExecutionService } from '../src/services/ExecutionService';
import { IServiceContainer } from '@stock-bot/di';
// Modify the data generation to ensure we get both golden and death crosses
function generateMockDataWithBothCrosses() {
const data = [];
const startDate = new Date('2023-01-01');
let price = 150;
// Create 365 days of data with clear trends
for (let i = 0; i < 365; i++) {
const date = new Date(startDate);
date.setDate(date.getDate() + i);
// Create alternating trends every ~30 days
const cycleDay = i % 60;
if (cycleDay < 30) {
// Uptrend - will create golden crosses
price += Math.random() * 2 - 0.5; // Bias upward
} else {
// Downtrend - will create death crosses
price -= Math.random() * 2 - 0.5; // Bias downward
}
// Add some volatility
const dayChange = (Math.random() - 0.5) * 4;
price = Math.max(100, Math.min(200, price + dayChange));
data.push({
timestamp: date.toISOString(),
open: price - Math.random() * 2,
high: price + Math.random() * 2,
low: price - Math.random() * 2,
close: price,
volume: Math.floor(Math.random() * 10000000) + 5000000
});
}
return data;
}
async function forceBothTradeTypes() {
// Create service container with minimal logging
const container: IServiceContainer = {
logger: {
info: (msg: string, ...args: any[]) => {
if (msg.includes('cross detected') || msg.includes('Generated') || msg.includes('Position update')) {
console.log('[INFO]', msg, ...args);
}
},
error: console.error,
warn: () => {},
debug: () => {},
} as any,
custom: {}
};
// Initialize services
const storageService = new StorageService();
const marketDataService = new MarketDataService(container);
const executionService = new ExecutionService(container);
const modeManager = new ModeManager(container, marketDataService, executionService, storageService);
const strategyManager = new StrategyManager(container);
container.custom = {
MarketDataService: marketDataService,
ExecutionService: executionService,
ModeManager: modeManager,
StorageService: storageService
};
// Override the mock data generation
const originalGenerateMockData = marketDataService.generateMockData;
marketDataService.generateMockData = function(symbol: string, startDate: string, endDate: string) {
console.log('Generating mock data with forced crossovers...');
return generateMockDataWithBothCrosses();
};
// Initialize backtest mode
await modeManager.initializeMode({
mode: 'backtest',
startDate: '2023-01-01T00:00:00Z',
endDate: '2024-01-01T00:00:00Z',
speed: 'max',
symbols: ['AAPL'],
initialCapital: 100000,
dataFrequency: '1d',
strategy: 'sma-crossover'
});
// Create backtest engine
const backtestEngine = new BacktestEngine(container, storageService, strategyManager);
// Configure backtest
const config = {
mode: 'backtest' as const,
name: 'Force Both Trade Types',
strategy: 'sma-crossover',
symbols: ['AAPL'],
startDate: '2023-01-01T00:00:00Z',
endDate: '2024-01-01T00:00:00Z',
initialCapital: 100000,
commission: 0.001,
slippage: 0.0001,
dataFrequency: '1d' as const,
speed: 'max' as const
};
console.log('=== TESTING WITH FORCED CROSSOVERS ===\n');
try {
const result = await backtestEngine.runBacktest(config);
console.log('\n=== BACKTEST COMPLETE ===');
console.log(`Total Trades: ${result.metrics.totalTrades}`);
// Analyze trades
let longTrades = 0;
let shortTrades = 0;
result.trades.forEach((trade) => {
if (trade.side === 'buy') {
longTrades++;
console.log(`\nLONG trade found:`);
console.log(` Entry: ${trade.entryDate} @ $${trade.entryPrice.toFixed(2)}`);
console.log(` Exit: ${trade.exitDate} @ $${trade.exitPrice.toFixed(2)}`);
console.log(` P&L: $${trade.pnl.toFixed(2)}`);
} else {
shortTrades++;
console.log(`\nSHORT trade found:`);
console.log(` Entry: ${trade.entryDate} @ $${trade.entryPrice.toFixed(2)}`);
console.log(` Exit: ${trade.exitDate} @ $${trade.exitPrice.toFixed(2)}`);
console.log(` P&L: $${trade.pnl.toFixed(2)}`);
}
});
console.log('\n=== FINAL SUMMARY ===');
console.log(`LONG trades: ${longTrades}`);
console.log(`SHORT trades: ${shortTrades}`);
console.log(`Total trades: ${result.metrics.totalTrades}`);
if (longTrades > 0 && shortTrades > 0) {
console.log('\n✅ SUCCESS: Both long and short trades are working!');
} else {
console.log('\n❌ ISSUE: Missing trade types');
if (longTrades === 0) console.log(' - No long trades found');
if (shortTrades === 0) console.log(' - No short trades found');
}
} catch (error) {
console.error('Test failed:', error);
}
}
// Run the test
forceBothTradeTypes().catch(console.error);

View file

@ -0,0 +1,99 @@
import { BacktestEngine } from '../src/backtest/BacktestEngine';
import { StrategyManager } from '../src/strategies/StrategyManager';
import { StorageService } from '../src/services/StorageService';
import { ModeManager } from '../src/core/ModeManager';
import { MarketDataService } from '../src/services/MarketDataService';
import { ExecutionService } from '../src/services/ExecutionService';
import { IServiceContainer } from '@stock-bot/di';
async function showOrderFlow() {
// Create service container
const container: IServiceContainer = {
logger: {
info: (msg: string, ...args: any[]) => {
// Only log order-related messages
if (msg.includes('order') || msg.includes('Order') || msg.includes('Filling') || msg.includes('cross detected')) {
console.log('[INFO]', msg, ...args);
}
},
error: (msg: string, ...args: any[]) => console.error('[ERROR]', msg, ...args),
warn: (msg: string, ...args: any[]) => {},
debug: (msg: string, ...args: any[]) => {},
} as any,
custom: {}
};
// Initialize services
const storageService = new StorageService();
const marketDataService = new MarketDataService(container);
const executionService = new ExecutionService(container);
const modeManager = new ModeManager(container, marketDataService, executionService, storageService);
const strategyManager = new StrategyManager(container);
// Set services in container
container.custom = {
MarketDataService: marketDataService,
ExecutionService: executionService,
ModeManager: modeManager,
StorageService: storageService
};
// Initialize backtest mode
await modeManager.initializeMode({
mode: 'backtest',
startDate: '2023-07-01T00:00:00Z',
endDate: '2023-08-01T00:00:00Z', // Just 1 month
speed: 'max',
symbols: ['AAPL'],
initialCapital: 100000,
dataFrequency: '1d',
strategy: 'sma-crossover'
});
// Create backtest engine
const backtestEngine = new BacktestEngine(container, storageService, strategyManager);
// Configure backtest
const config = {
mode: 'backtest' as const,
name: 'Order Flow Demo',
strategy: 'sma-crossover',
symbols: ['AAPL'],
startDate: '2023-07-01T00:00:00Z',
endDate: '2023-08-01T00:00:00Z',
initialCapital: 100000,
commission: 0.001,
slippage: 0.0001,
dataFrequency: '1d' as const,
speed: 'max' as const
};
console.log('\n=== ORDER FLOW DEMONSTRATION ===\n');
console.log('This will show the actual BUY and SELL orders being generated and executed:\n');
try {
const result = await backtestEngine.runBacktest(config);
console.log('\n=== COMPLETED TRADES (Round-trips) ===');
console.log('Note: Each trade below represents a BUY order (entry) + SELL order (exit):\n');
result.trades.forEach((trade, i) => {
console.log(`Trade ${i + 1}:`);
console.log(` Opened with: BUY order on ${trade.entryDate} @ $${trade.entryPrice.toFixed(2)}`);
console.log(` Closed with: SELL order on ${trade.exitDate} @ $${trade.exitPrice.toFixed(2)}`);
console.log(` Trade type: ${trade.side === 'buy' ? 'LONG' : 'SHORT'} position`);
console.log(` P&L: $${trade.pnl.toFixed(2)} (${trade.pnlPercent.toFixed(2)}%)\n`);
});
console.log('Summary:');
console.log(`- Total completed trades: ${result.metrics.totalTrades}`);
console.log(`- Each trade = 1 BUY order + 1 SELL order`);
console.log(`- Total orders executed: ${result.metrics.totalTrades * 2}`);
} catch (error) {
console.error('Failed:', error);
}
}
// Run the demo
showOrderFlow().catch(console.error);

View file

@ -0,0 +1,103 @@
import { BacktestEngine } from '../src/backtest/BacktestEngine';
import { StrategyManager } from '../src/strategies/StrategyManager';
import { StorageService } from '../src/services/StorageService';
import { ModeManager } from '../src/core/ModeManager';
import { MarketDataService } from '../src/services/MarketDataService';
import { ExecutionService } from '../src/services/ExecutionService';
import { IServiceContainer } from '@stock-bot/di';
async function testShortTrades() {
// Create service container
const container: IServiceContainer = {
logger: {
info: () => {},
error: console.error,
warn: () => {},
debug: () => {},
} as any,
custom: {}
};
// Initialize services
const storageService = new StorageService();
const marketDataService = new MarketDataService(container);
const executionService = new ExecutionService(container);
const modeManager = new ModeManager(container, marketDataService, executionService, storageService);
const strategyManager = new StrategyManager(container);
container.custom = {
MarketDataService: marketDataService,
ExecutionService: executionService,
ModeManager: modeManager,
StorageService: storageService
};
// Initialize backtest mode
await modeManager.initializeMode({
mode: 'backtest',
startDate: '2023-01-01T00:00:00Z',
endDate: '2024-01-01T00:00:00Z',
speed: 'max',
symbols: ['AAPL'],
initialCapital: 100000,
dataFrequency: '1d',
strategy: 'sma-crossover'
});
// Create backtest engine
const backtestEngine = new BacktestEngine(container, storageService, strategyManager);
// Configure backtest
const config = {
mode: 'backtest' as const,
name: 'Short Trade Test',
strategy: 'sma-crossover',
symbols: ['AAPL'],
startDate: '2023-01-01T00:00:00Z',
endDate: '2024-01-01T00:00:00Z',
initialCapital: 100000,
commission: 0.001,
slippage: 0.0001,
dataFrequency: '1d' as const,
speed: 'max' as const
};
console.log('Testing short trade recording...\n');
try {
const result = await backtestEngine.runBacktest(config);
console.log('=== BACKTEST RESULTS ===');
console.log(`Total Trades: ${result.metrics.totalTrades}`);
console.log(`Win Rate: ${result.metrics.winRate.toFixed(2)}%`);
console.log(`Total Return: ${result.metrics.totalReturn.toFixed(2)}%`);
// Count long vs short trades
let longTrades = 0;
let shortTrades = 0;
console.log('\n=== TRADE BREAKDOWN ===');
result.trades.forEach((trade, i) => {
const tradeType = trade.side === 'buy' ? 'LONG' : 'SHORT';
if (trade.side === 'buy') longTrades++;
else shortTrades++;
console.log(`Trade ${i + 1} (${tradeType}):`);
console.log(` Entry: ${trade.entryDate} @ $${trade.entryPrice.toFixed(2)}`);
console.log(` Exit: ${trade.exitDate} @ $${trade.exitPrice.toFixed(2)}`);
console.log(` P&L: $${trade.pnl.toFixed(2)} (${trade.pnlPercent.toFixed(2)}%)`);
console.log('');
});
console.log('=== TRADE TYPE SUMMARY ===');
console.log(`Long trades: ${longTrades}`);
console.log(`Short trades: ${shortTrades}`);
console.log(`Total trades: ${result.metrics.totalTrades}`);
} catch (error) {
console.error('Test failed:', error);
}
}
// Run the test
testShortTrades().catch(console.error);

View file

@ -0,0 +1,188 @@
import { BacktestEngine } from '../src/backtest/BacktestEngine';
import { StrategyManager } from '../src/strategies/StrategyManager';
import { StorageService } from '../src/services/StorageService';
import { ModeManager } from '../src/core/ModeManager';
import { MarketDataService } from '../src/services/MarketDataService';
import { ExecutionService } from '../src/services/ExecutionService';
import { IServiceContainer } from '@stock-bot/di';
async function traceShortPositions() {
let orderCount = 0;
let fillCount = 0;
const orderTracker: any[] = [];
const fillTracker: any[] = [];
// Create service container with detailed logging
const container: IServiceContainer = {
logger: {
info: (msg: string, ...args: any[]) => {
// Log all order and fill events
if (msg.includes('order') || msg.includes('Order') ||
msg.includes('fill') || msg.includes('Fill') ||
msg.includes('position') || msg.includes('Position')) {
console.log('[INFO]', msg, ...args);
}
// Track order submissions
if (msg.includes('Submitting order')) {
orderCount++;
const orderData = args[0];
orderTracker.push({
count: orderCount,
symbol: orderData.symbol,
side: orderData.side,
quantity: orderData.quantity,
type: orderData.type
});
console.log(`\n🔵 ORDER #${orderCount}:`, orderData.side.toUpperCase(), orderData.symbol);
}
// Track fills
if (msg.includes('Order filled')) {
fillCount++;
const fillData = args[0];
fillTracker.push({
count: fillCount,
orderId: fillData.orderId,
symbol: fillData.symbol,
side: fillData.side,
price: fillData.price,
quantity: fillData.quantity
});
console.log(`\n🟢 FILL #${fillCount}:`, fillData.side.toUpperCase(), fillData.symbol, '@', fillData.price);
}
},
error: console.error,
warn: () => {},
debug: () => {},
} as any,
custom: {}
};
// Initialize services
const storageService = new StorageService();
const marketDataService = new MarketDataService(container);
const executionService = new ExecutionService(container);
const modeManager = new ModeManager(container, marketDataService, executionService, storageService);
const strategyManager = new StrategyManager(container);
container.custom = {
MarketDataService: marketDataService,
ExecutionService: executionService,
ModeManager: modeManager,
StorageService: storageService
};
// Listen to strategy events
strategyManager.on('signal', (signal: any) => {
console.log('\n🎯 STRATEGY SIGNAL:', signal);
});
// Initialize backtest mode
await modeManager.initializeMode({
mode: 'backtest',
startDate: '2023-06-01T00:00:00Z',
endDate: '2023-09-01T00:00:00Z', // 3 months
speed: 'max',
symbols: ['AAPL'],
initialCapital: 100000,
dataFrequency: '1d',
strategy: 'sma-crossover'
});
// Create backtest engine
const backtestEngine = new BacktestEngine(container, storageService, strategyManager);
// Configure backtest
const config = {
mode: 'backtest' as const,
name: 'Short Position Trace',
strategy: 'sma-crossover',
symbols: ['AAPL'],
startDate: '2023-06-01T00:00:00Z',
endDate: '2023-09-01T00:00:00Z',
initialCapital: 100000,
commission: 0.001,
slippage: 0.0001,
dataFrequency: '1d' as const,
speed: 'max' as const
};
console.log('=== TRACING SHORT POSITION FLOW ===\n');
try {
const result = await backtestEngine.runBacktest(config);
console.log('\n=== ORDER/FILL TRACKING SUMMARY ===');
console.log(`Total orders submitted: ${orderCount}`);
console.log(`Total fills executed: ${fillCount}`);
// Analyze order flow
let buyOrders = 0;
let sellOrders = 0;
orderTracker.forEach(order => {
if (order.side === 'buy') buyOrders++;
else sellOrders++;
});
console.log(`\nOrder breakdown:`);
console.log(`- BUY orders: ${buyOrders}`);
console.log(`- SELL orders: ${sellOrders}`);
// Check for short positions
console.log('\n=== LOOKING FOR SHORT PATTERNS ===');
console.log('(A short position starts with a SELL and closes with a BUY)\n');
let potentialShorts = 0;
for (let i = 0; i < orderTracker.length - 1; i++) {
if (orderTracker[i].side === 'sell') {
// Look for subsequent buy to close
for (let j = i + 1; j < orderTracker.length; j++) {
if (orderTracker[j].side === 'buy' && orderTracker[j].symbol === orderTracker[i].symbol) {
potentialShorts++;
console.log(`Potential short trade found:`);
console.log(` Open: Order #${orderTracker[i].count} (SELL)`);
console.log(` Close: Order #${orderTracker[j].count} (BUY)`);
break;
}
}
}
}
console.log(`\nPotential short trades identified: ${potentialShorts}`);
// Final results
console.log('\n=== BACKTEST RESULTS ===');
console.log(`Total closed trades: ${result.metrics.totalTrades}`);
let longTrades = 0;
let shortTrades = 0;
result.trades.forEach((trade, i) => {
const tradeType = trade.side === 'buy' ? 'LONG' : 'SHORT';
if (trade.side === 'buy') longTrades++;
else shortTrades++;
console.log(`\nTrade ${i + 1} (${tradeType}):`);
console.log(` Entry: ${trade.entryDate} @ $${trade.entryPrice.toFixed(2)}`);
console.log(` Exit: ${trade.exitDate} @ $${trade.exitPrice.toFixed(2)}`);
console.log(` P&L: $${trade.pnl.toFixed(2)}`);
});
console.log('\n=== FINAL SUMMARY ===');
console.log(`Long trades recorded: ${longTrades}`);
console.log(`Short trades recorded: ${shortTrades}`);
console.log(`Expected short trades (from order flow): ${potentialShorts}`);
if (shortTrades < potentialShorts) {
console.log('\n❌ ISSUE: Not all short trades were recorded in closed trades!');
console.log('This suggests the Rust core trade matching logic may have an issue.');
}
} catch (error) {
console.error('Trace failed:', error);
}
}
// Run the trace
traceShortPositions().catch(console.error);

View file

@ -0,0 +1,133 @@
import { BacktestEngine } from '../src/backtest/BacktestEngine';
import { StrategyManager } from '../src/strategies/StrategyManager';
import { StorageService } from '../src/services/StorageService';
import { ModeManager } from '../src/core/ModeManager';
import { MarketDataService } from '../src/services/MarketDataService';
import { ExecutionService } from '../src/services/ExecutionService';
import { IServiceContainer } from '@stock-bot/di';
async function verifyBothTradeTypes() {
// Create service container
const container: IServiceContainer = {
logger: {
info: console.log,
error: console.error,
warn: console.warn,
debug: console.log,
} as any,
custom: {}
};
// Initialize services
const storageService = new StorageService();
const marketDataService = new MarketDataService(container);
const executionService = new ExecutionService(container);
const modeManager = new ModeManager(container, marketDataService, executionService, storageService);
const strategyManager = new StrategyManager(container);
container.custom = {
MarketDataService: marketDataService,
ExecutionService: executionService,
ModeManager: modeManager,
StorageService: storageService
};
// Initialize backtest mode
await modeManager.initializeMode({
mode: 'backtest',
startDate: '2023-01-01T00:00:00Z',
endDate: '2024-01-01T00:00:00Z',
speed: 'max',
symbols: ['AAPL'],
initialCapital: 100000,
dataFrequency: '1d',
strategy: 'sma-crossover'
});
// Create backtest engine
const backtestEngine = new BacktestEngine(container, storageService, strategyManager);
// Configure backtest
const config = {
mode: 'backtest' as const,
name: 'Trade Type Verification',
strategy: 'sma-crossover',
symbols: ['AAPL'],
startDate: '2023-01-01T00:00:00Z',
endDate: '2024-01-01T00:00:00Z',
initialCapital: 100000,
commission: 0.001,
slippage: 0.0001,
dataFrequency: '1d' as const,
speed: 'max' as const
};
console.log('=== VERIFYING BOTH LONG AND SHORT TRADES ===\n');
try {
const result = await backtestEngine.runBacktest(config);
console.log('=== BACKTEST COMPLETE ===');
console.log(`Total Trades: ${result.metrics.totalTrades}`);
console.log(`Win Rate: ${result.metrics.winRate.toFixed(2)}%`);
console.log(`Total Return: ${result.metrics.totalReturn.toFixed(2)}%`);
// Analyze trades
let longTrades = 0;
let shortTrades = 0;
let longPnL = 0;
let shortPnL = 0;
console.log('\n=== DETAILED TRADE ANALYSIS ===');
result.trades.forEach((trade, i) => {
const isLong = trade.side === 'buy';
const tradeType = isLong ? 'LONG' : 'SHORT';
if (isLong) {
longTrades++;
longPnL += trade.pnl;
} else {
shortTrades++;
shortPnL += trade.pnl;
}
console.log(`\nTrade ${i + 1}:`);
console.log(` Type: ${tradeType} position`);
console.log(` Opening side: ${trade.side.toUpperCase()}`);
console.log(` Entry: ${trade.entryDate} @ $${trade.entryPrice.toFixed(2)}`);
console.log(` Exit: ${trade.exitDate} @ $${trade.exitPrice.toFixed(2)}`);
console.log(` P&L: $${trade.pnl.toFixed(2)} (${trade.pnlPercent.toFixed(2)}%)`);
// Explain the trade flow
if (isLong) {
console.log(` Order flow: BUY ${trade.quantity} shares → SELL ${trade.quantity} shares`);
} else {
console.log(` Order flow: SELL ${trade.quantity} shares → BUY ${trade.quantity} shares`);
}
});
console.log('\n=== SUMMARY BY TRADE TYPE ===');
console.log(`LONG trades: ${longTrades}`);
console.log(` - Total P&L from longs: $${longPnL.toFixed(2)}`);
console.log(` - Average P&L per long: $${longTrades > 0 ? (longPnL / longTrades).toFixed(2) : '0.00'}`);
console.log(`\nSHORT trades: ${shortTrades}`);
console.log(` - Total P&L from shorts: $${shortPnL.toFixed(2)}`);
console.log(` - Average P&L per short: $${shortTrades > 0 ? (shortPnL / shortTrades).toFixed(2) : '0.00'}`);
console.log(`\nTotal trades: ${result.metrics.totalTrades}`);
if (longTrades === 0) {
console.log('\n⚠ WARNING: No long trades found!');
}
if (shortTrades === 0) {
console.log('\n⚠ WARNING: No short trades found!');
}
} catch (error) {
console.error('Test failed:', error);
}
}
// Run the verification
verifyBothTradeTypes().catch(console.error);

View file

@ -89,7 +89,7 @@ export class StrategyManager extends EventEmitter {
case 'smacrossover':
case 'sma-crossover':
case 'moving-average': {
const { SimpleMovingAverageCrossover } = await import('./examples/SimpleMovingAverageCrossover');
const { SimpleMovingAverageCrossover } = await import('./examples/SimpleMovingAverageCrossoverFixed');
strategy = new SimpleMovingAverageCrossover(
config,
this.container.custom?.ModeManager,

View file

@ -110,9 +110,8 @@ export class SimpleMovingAverageCrossover extends BaseStrategy {
}
}
if (barsSinceLastTrade < this.MIN_HOLDING_BARS && lastTradeBar > 0) {
return null; // Too soon to trade again
}
// Note: We removed the MIN_HOLDING_BARS check here to allow closing and opening on same bar
// The check is now done individually for new position entries
if (goldenCross) {
logger.info(`🟢 Golden cross detected for ${symbol} @ ${timestamp}`);
@ -144,6 +143,7 @@ export class SimpleMovingAverageCrossover extends BaseStrategy {
return signal;
} else if (currentPosition === 0) {
// No position, open long
logger.info(` No current position, opening long`);
logger.info(` Price: ${currentPrice.toFixed(2)}, Fast MA: ${fastMA.toFixed(2)} > Slow MA: ${slowMA.toFixed(2)}`);
logger.info(` Prev Fast MA: ${prevFastMA.toFixed(2)} <= Prev Slow MA: ${prevSlowMA.toFixed(2)}`);
@ -207,6 +207,8 @@ export class SimpleMovingAverageCrossover extends BaseStrategy {
return signal;
} else if (currentPosition === 0) {
// Open short position for long/short strategy
logger.info(` No current position, opening short`);
const positionSize = this.calculatePositionSize(currentPrice);
logger.info(` Opening short position: ${positionSize} shares`);

View file

@ -0,0 +1,305 @@
import { BaseStrategy, Signal } from '../BaseStrategy';
import { MarketData } from '../../types';
import { getLogger } from '@stock-bot/logger';
const logger = getLogger('SimpleMovingAverageCrossover');
export class SimpleMovingAverageCrossover extends BaseStrategy {
private priceHistory = new Map<string, number[]>();
private lastSignalBar = new Map<string, number>();
private barCount = new Map<string, number>();
private totalSignals = 0;
private lastCrossoverType = new Map<string, 'golden' | 'death' | null>();
// Strategy parameters
private readonly FAST_PERIOD = 5; // Changed from 10 to generate more signals
private readonly SLOW_PERIOD = 15; // Changed from 20 to generate more signals
private readonly POSITION_SIZE = 0.1; // 10% of capital per position
private readonly MIN_HOLDING_BARS = 3; // Minimum bars to hold position
private readonly DEBUG_INTERVAL = 10; // Log every N bars for debugging
constructor(config: any, modeManager?: any, executionService?: any) {
super(config, modeManager, executionService);
logger.info(`SimpleMovingAverageCrossover initialized with Fast=${this.FAST_PERIOD}, Slow=${this.SLOW_PERIOD}`);
}
protected updateIndicators(data: MarketData): void {
if (data.type !== 'bar') return;
const symbol = data.data.symbol;
const price = data.data.close;
// Initialize or get price history
if (!this.priceHistory.has(symbol)) {
this.priceHistory.set(symbol, []);
this.barCount.set(symbol, 0);
logger.info(`📊 Starting to track ${symbol} @ ${price.toFixed(2)}`);
}
const history = this.priceHistory.get(symbol)!;
history.push(price);
// Increment bar count
const currentBar = (this.barCount.get(symbol) || 0) + 1;
this.barCount.set(symbol, currentBar);
// Keep only needed history
if (history.length > this.SLOW_PERIOD * 2) {
history.shift();
}
// Log when we have enough data to start trading
if (history.length === this.SLOW_PERIOD) {
logger.info(`${symbol} now has enough history (${history.length} bars) to start trading`);
}
}
protected async generateSignal(data: MarketData): Promise<Signal | null> {
if (data.type !== 'bar') return null;
const symbol = data.data.symbol;
const history = this.priceHistory.get(symbol);
if (!history || history.length < this.SLOW_PERIOD) {
return null;
}
const currentPrice = data.data.close;
const timestamp = data.data.timestamp;
const currentBar = this.barCount.get(symbol) || 0;
// Calculate moving averages
const fastMA = this.calculateSMA(history, this.FAST_PERIOD);
const slowMA = this.calculateSMA(history, this.SLOW_PERIOD);
// Get previous MAs for crossover detection
const historyWithoutLast = history.slice(0, -1);
const prevFastMA = this.calculateSMA(historyWithoutLast, this.FAST_PERIOD);
const prevSlowMA = this.calculateSMA(historyWithoutLast, this.SLOW_PERIOD);
// Detect crossovers
const goldenCross = prevFastMA <= prevSlowMA && fastMA > slowMA;
const deathCross = prevFastMA >= prevSlowMA && fastMA < slowMA;
// Get current position
const currentPosition = this.getPosition(symbol);
// Track last signal bar
const lastSignalBar = this.lastSignalBar.get(symbol) || 0;
const barsSinceLastSignal = currentBar - lastSignalBar;
// Calculate MA difference for debugging
const maDiff = fastMA - slowMA;
const maDiffPct = (maDiff / slowMA) * 100;
// Debug logging every N bars
if (currentBar % this.DEBUG_INTERVAL === 0 || goldenCross || deathCross) {
logger.info(`${symbol} @ ${timestamp} [Bar ${currentBar}]:`);
logger.info(` Price: $${currentPrice.toFixed(2)}`);
logger.info(` Fast MA (${this.FAST_PERIOD}): $${fastMA.toFixed(2)}`);
logger.info(` Slow MA (${this.SLOW_PERIOD}): $${slowMA.toFixed(2)}`);
logger.info(` MA Diff: ${maDiff.toFixed(2)} (${maDiffPct.toFixed(2)}%)`);
logger.info(` Position: ${currentPosition} shares`);
logger.info(` Bars since last signal: ${barsSinceLastSignal}`);
if (goldenCross) {
logger.info(` 🟢 GOLDEN CROSS DETECTED!`);
}
if (deathCross) {
logger.info(` 🔴 DEATH CROSS DETECTED!`);
}
}
// Store crossover type for position management
if (goldenCross) {
this.lastCrossoverType.set(symbol, 'golden');
} else if (deathCross) {
this.lastCrossoverType.set(symbol, 'death');
}
// Check if we should enter a position based on the current trend
const lastCrossover = this.lastCrossoverType.get(symbol);
if (currentPosition === 0 && barsSinceLastSignal >= this.MIN_HOLDING_BARS) {
// No position - check if we should enter based on last crossover
if (lastCrossover === 'golden' && fastMA > slowMA) {
// Trend is still bullish after golden cross - open long
logger.info(`🟢 Opening LONG position - Bullish trend after golden cross`);
logger.info(` Current position: 0 shares`);
const positionSize = this.calculatePositionSize(currentPrice);
logger.info(` Opening long position: ${positionSize} shares`);
const signal: Signal = {
type: 'buy',
symbol,
strength: 0.8,
reason: 'Bullish trend - Opening long position',
metadata: {
fastMA,
slowMA,
prevFastMA,
prevSlowMA,
crossoverType: 'trend_long',
price: currentPrice,
quantity: positionSize
}
};
this.lastSignalBar.set(symbol, currentBar);
this.totalSignals++;
logger.info(`👉 Total signals generated: ${this.totalSignals}`);
return signal;
} else if (lastCrossover === 'death' && fastMA < slowMA) {
// Trend is still bearish after death cross - open short
logger.info(`🔴 Opening SHORT position - Bearish trend after death cross`);
logger.info(` Current position: 0 shares`);
const positionSize = this.calculatePositionSize(currentPrice);
logger.info(` Opening short position: ${positionSize} shares`);
const signal: Signal = {
type: 'sell',
symbol,
strength: 0.8,
reason: 'Bearish trend - Opening short position',
metadata: {
fastMA,
slowMA,
prevFastMA,
prevSlowMA,
crossoverType: 'trend_short',
price: currentPrice,
quantity: positionSize
}
};
this.lastSignalBar.set(symbol, currentBar);
this.totalSignals++;
logger.info(`👉 Total signals generated: ${this.totalSignals}`);
return signal;
}
}
// Handle crossovers for position changes
if (goldenCross) {
logger.info(`🟢 Golden cross detected for ${symbol} @ ${timestamp}`);
logger.info(` Current position: ${currentPosition} shares`);
if (currentPosition < 0) {
// Close short position
logger.info(` Closing short position of ${Math.abs(currentPosition)} shares`);
const signal: Signal = {
type: 'buy',
symbol,
strength: 0.8,
reason: 'Golden cross - Closing short position',
metadata: {
fastMA,
slowMA,
prevFastMA,
prevSlowMA,
crossoverType: 'golden',
price: currentPrice,
quantity: Math.abs(currentPosition)
}
};
this.lastSignalBar.set(symbol, currentBar);
this.totalSignals++;
logger.info(`👉 Total signals generated: ${this.totalSignals}`);
return signal;
}
} else if (deathCross) {
logger.info(`🔴 Death cross detected for ${symbol} @ ${timestamp}`);
logger.info(` Current position: ${currentPosition} shares`);
if (currentPosition > 0) {
// Close long position
logger.info(` Closing long position of ${currentPosition} shares`);
const signal: Signal = {
type: 'sell',
symbol,
strength: 0.8,
reason: 'Death cross - Closing long position',
metadata: {
fastMA,
slowMA,
prevFastMA,
prevSlowMA,
crossoverType: 'death',
price: currentPrice,
quantity: currentPosition
}
};
this.lastSignalBar.set(symbol, currentBar);
this.totalSignals++;
logger.info(`👉 Total signals generated: ${this.totalSignals}`);
return signal;
}
}
return null;
}
private calculateSMA(prices: number[], period: number): number {
if (prices.length < period) {
logger.warn(`Not enough data for SMA calculation: ${prices.length} < ${period}`);
return 0;
}
const slice = prices.slice(-period);
const sum = slice.reduce((a, b) => a + b, 0);
const sma = sum / period;
// Sanity check
if (isNaN(sma) || !isFinite(sma)) {
logger.error(`Invalid SMA calculation: sum=${sum}, period=${period}, prices=${slice.length}`);
return 0;
}
return sma;
}
private calculatePositionSize(price: number): number {
// Get account balance from trading engine
const tradingEngine = this.modeManager?.getTradingEngine();
if (!tradingEngine) {
logger.warn('No trading engine available, using default position size');
return 100;
}
// Try to get account balance from trading engine
let accountBalance = 100000; // Default
try {
if (tradingEngine.getAccountBalance) {
accountBalance = tradingEngine.getAccountBalance();
} else if (tradingEngine.getTotalPnl) {
const [realized, unrealized] = tradingEngine.getTotalPnl();
accountBalance = 100000 + realized + unrealized; // Assuming 100k initial
}
} catch (error) {
logger.warn('Could not get account balance:', error);
}
const positionValue = accountBalance * this.POSITION_SIZE;
const shares = Math.floor(positionValue / price);
logger.debug(`Position sizing: Balance=$${accountBalance}, Position Size=${this.POSITION_SIZE}, Price=$${price}, Shares=${shares}`);
return Math.max(1, shares); // At least 1 share
}
protected onOrderFilled(fill: any): void {
const { symbol, side, quantity, price } = fill;
logger.info(`${side.toUpperCase()} filled: ${symbol} - ${quantity} shares @ ${price}`);
// Update our internal position tracking to match
// This is redundant with BaseStrategy but helps with debugging
const currentPosition = this.getPosition(symbol);
logger.info(` Position after fill: ${currentPosition} shares`);
}
}