fixed test strat
This commit is contained in:
parent
6cf3179092
commit
8a9a4bc336
12 changed files with 1301 additions and 8 deletions
113
apps/stock/orchestrator/examples/debug-position-tracking.ts
Normal file
113
apps/stock/orchestrator/examples/debug-position-tracking.ts
Normal 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);
|
||||
129
apps/stock/orchestrator/examples/debug-rust-short-trades.ts
Normal file
129
apps/stock/orchestrator/examples/debug-rust-short-trades.ts
Normal 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);
|
||||
161
apps/stock/orchestrator/examples/force-both-trade-types.ts
Normal file
161
apps/stock/orchestrator/examples/force-both-trade-types.ts
Normal 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);
|
||||
99
apps/stock/orchestrator/examples/show-order-flow.ts
Normal file
99
apps/stock/orchestrator/examples/show-order-flow.ts
Normal 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);
|
||||
103
apps/stock/orchestrator/examples/test-short-trades.ts
Normal file
103
apps/stock/orchestrator/examples/test-short-trades.ts
Normal 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);
|
||||
188
apps/stock/orchestrator/examples/trace-short-positions.ts
Normal file
188
apps/stock/orchestrator/examples/trace-short-positions.ts
Normal 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);
|
||||
133
apps/stock/orchestrator/examples/verify-both-trade-types.ts
Normal file
133
apps/stock/orchestrator/examples/verify-both-trade-types.ts
Normal 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);
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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`);
|
||||
|
||||
|
|
|
|||
|
|
@ -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`);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue