small fixes for backtest
This commit is contained in:
parent
6df32dc18b
commit
6cf3179092
16 changed files with 2180 additions and 16 deletions
272
apps/stock/orchestrator/examples/advanced-risk-management.ts
Normal file
272
apps/stock/orchestrator/examples/advanced-risk-management.ts
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
/**
|
||||
* Advanced Risk Management Examples
|
||||
* Demonstrates orderbook analytics, portfolio risk, and bet sizing
|
||||
*/
|
||||
|
||||
import { TradingEngine, RiskAnalyzer, OrderbookAnalyzer } from '@stock-bot/core';
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('AdvancedRiskExample');
|
||||
|
||||
// Example 1: Orderbook Analytics
|
||||
async function orderbookAnalyticsExample() {
|
||||
console.log('\n=== Orderbook Analytics Example ===');
|
||||
|
||||
const engine = new TradingEngine('paper', { startingCapital: 100000 });
|
||||
const obAnalyzer = new OrderbookAnalyzer();
|
||||
|
||||
// Update orderbook with some data
|
||||
const symbol = 'AAPL';
|
||||
engine.updateQuote(symbol, 149.95, 150.05, 1000, 1200);
|
||||
engine.updateQuote(symbol, 149.90, 150.10, 800, 900);
|
||||
engine.updateQuote(symbol, 149.85, 150.15, 600, 700);
|
||||
|
||||
// Get orderbook snapshot
|
||||
const snapshotJson = engine.getOrderbookSnapshot(symbol, 10);
|
||||
const snapshot = JSON.parse(snapshotJson);
|
||||
|
||||
// Analyze orderbook
|
||||
const analyticsJson = obAnalyzer.analyzeOrderbook(snapshotJson);
|
||||
const analytics = JSON.parse(analyticsJson);
|
||||
|
||||
console.log('Orderbook Analytics:');
|
||||
console.log(` Spread: $${analytics.spread.toFixed(2)} (${analytics.spread_bps.toFixed(1)} bps)`);
|
||||
console.log(` Mid Price: $${analytics.mid_price.toFixed(2)}`);
|
||||
console.log(` Micro Price: $${analytics.micro_price.toFixed(2)}`);
|
||||
console.log(` Imbalance: ${(analytics.imbalance * 100).toFixed(1)}%`);
|
||||
console.log(` Liquidity Score: ${analytics.liquidity_score.toFixed(2)}`);
|
||||
|
||||
// Calculate liquidity profile
|
||||
const profileJson = obAnalyzer.calculateLiquidityProfile(snapshotJson);
|
||||
const profile = JSON.parse(profileJson);
|
||||
|
||||
console.log('\nLiquidity Profile:');
|
||||
console.log(` Total Bid Depth: $${profile.total_bid_depth.toFixed(2)}`);
|
||||
console.log(` Total Ask Depth: $${profile.total_ask_depth.toFixed(2)}`);
|
||||
|
||||
// Calculate market impact for a $10,000 buy order
|
||||
const impactJson = obAnalyzer.calculateMarketImpact(snapshotJson, 10000, true);
|
||||
const impact = JSON.parse(impactJson);
|
||||
|
||||
console.log('\nMarket Impact ($10k buy):');
|
||||
console.log(` Avg Execution Price: $${impact.avg_execution_price.toFixed(2)}`);
|
||||
console.log(` Price Impact: ${(impact.price_impact * 100).toFixed(2)}%`);
|
||||
console.log(` Slippage: $${impact.slippage.toFixed(2)}`);
|
||||
console.log(` Levels Consumed: ${impact.levels_consumed}`);
|
||||
}
|
||||
|
||||
// Example 2: Portfolio Risk Analysis
|
||||
async function portfolioRiskExample() {
|
||||
console.log('\n=== Portfolio Risk Analysis Example ===');
|
||||
|
||||
const riskAnalyzer = new RiskAnalyzer(100000, 0.02, 252); // $100k, 2% risk, 252 days lookback
|
||||
|
||||
// Update historical returns for portfolio symbols
|
||||
const symbols = ['AAPL', 'GOOGL', 'MSFT', 'AMZN', 'META'];
|
||||
|
||||
// Simulate some returns data (in practice, load from historical data)
|
||||
for (const symbol of symbols) {
|
||||
const returns = generateRandomReturns(252);
|
||||
riskAnalyzer.updateReturns(symbol, returns);
|
||||
}
|
||||
|
||||
// Current positions
|
||||
const positions = [
|
||||
{ symbol: 'AAPL', quantity: 100, avgPrice: 150 },
|
||||
{ symbol: 'GOOGL', quantity: 50, avgPrice: 140 },
|
||||
{ symbol: 'MSFT', quantity: 75, avgPrice: 380 },
|
||||
{ symbol: 'AMZN', quantity: 40, avgPrice: 170 },
|
||||
{ symbol: 'META', quantity: 60, avgPrice: 480 }
|
||||
];
|
||||
|
||||
// Current prices
|
||||
const prices = {
|
||||
AAPL: 155,
|
||||
GOOGL: 145,
|
||||
MSFT: 390,
|
||||
AMZN: 175,
|
||||
META: 490
|
||||
};
|
||||
|
||||
// Calculate portfolio risk
|
||||
const riskJson = riskAnalyzer.calculatePortfolioRisk(
|
||||
JSON.stringify(positions.map(p => [p.symbol, p.quantity, p.avgPrice])),
|
||||
JSON.stringify(prices)
|
||||
);
|
||||
const risk = JSON.parse(riskJson);
|
||||
|
||||
console.log('Portfolio Risk Metrics:');
|
||||
console.log(` VaR (95%): $${risk.total_var_95.toFixed(2)}`);
|
||||
console.log(` VaR (99%): $${risk.total_var_99.toFixed(2)}`);
|
||||
console.log(` CVaR (95%): $${risk.total_cvar_95.toFixed(2)}`);
|
||||
|
||||
console.log('\nConcentration Metrics:');
|
||||
console.log(` Herfindahl Index: ${risk.concentration_risk.herfindahl_index.toFixed(3)}`);
|
||||
console.log(` Effective Positions: ${risk.concentration_risk.effective_number_of_positions.toFixed(1)}`);
|
||||
console.log(` Top 5 Concentration: ${(risk.concentration_risk.top_5_concentration * 100).toFixed(1)}%`);
|
||||
|
||||
console.log('\nCorrelation Analysis:');
|
||||
console.log(` Average Correlation: ${risk.correlation_matrix.average_correlation.toFixed(3)}`);
|
||||
console.log(` Max Correlation: ${risk.correlation_matrix.max_correlation[0]} vs ${risk.correlation_matrix.max_correlation[1]} = ${risk.correlation_matrix.max_correlation[2].toFixed(3)}`);
|
||||
console.log(` Clustering Score: ${risk.correlation_matrix.clustering_score.toFixed(3)}`);
|
||||
|
||||
console.log('\nStress Test Results:');
|
||||
for (const [scenario, loss] of Object.entries(risk.stress_test_results)) {
|
||||
console.log(` ${scenario}: $${(loss as number).toFixed(2)}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Example 3: Dynamic Bet Sizing
|
||||
async function betSizingExample() {
|
||||
console.log('\n=== Dynamic Bet Sizing Example ===');
|
||||
|
||||
const riskAnalyzer = new RiskAnalyzer(100000, 0.02, 252);
|
||||
|
||||
// Scenario 1: Strong signal in trending market
|
||||
console.log('\nScenario 1: Strong Signal, Trending Market');
|
||||
let positionSize = riskAnalyzer.calculatePositionSize(
|
||||
0.8, // signal_strength
|
||||
0.9, // signal_confidence
|
||||
0.015, // volatility (1.5% daily)
|
||||
0.8, // liquidity_score
|
||||
0.02, // current_drawdown (2%)
|
||||
150, // price
|
||||
145, // stop_loss
|
||||
'trending'
|
||||
);
|
||||
|
||||
let size = JSON.parse(positionSize);
|
||||
console.log(` Shares: ${size.shares}`);
|
||||
console.log(` Notional Value: $${size.notional_value.toFixed(2)}`);
|
||||
console.log(` % of Capital: ${(size.percent_of_capital * 100).toFixed(2)}%`);
|
||||
console.log(' Adjustments:');
|
||||
for (const adj of size.adjustments) {
|
||||
console.log(` ${adj.reason}: ${adj.factor.toFixed(2)}x`);
|
||||
}
|
||||
|
||||
// Scenario 2: Weak signal in high volatility
|
||||
console.log('\nScenario 2: Weak Signal, High Volatility');
|
||||
positionSize = riskAnalyzer.calculatePositionSize(
|
||||
0.3, // signal_strength
|
||||
0.5, // signal_confidence
|
||||
0.03, // volatility (3% daily)
|
||||
0.6, // liquidity_score
|
||||
0.15, // current_drawdown (15%)
|
||||
150, // price
|
||||
null, // no stop_loss
|
||||
'high_volatility'
|
||||
);
|
||||
|
||||
size = JSON.parse(positionSize);
|
||||
console.log(` Shares: ${size.shares}`);
|
||||
console.log(` Notional Value: $${size.notional_value.toFixed(2)}`);
|
||||
console.log(` % of Capital: ${(size.percent_of_capital * 100).toFixed(2)}%`);
|
||||
|
||||
// Calculate optimal stop loss
|
||||
const supportLevels = [148, 145, 142, 140];
|
||||
const optimalStop = riskAnalyzer.calculateOptimalStopLoss(
|
||||
150, // entry_price
|
||||
0.015, // volatility
|
||||
supportLevels,
|
||||
2.5, // atr
|
||||
true // is_long
|
||||
);
|
||||
|
||||
console.log(`\nOptimal Stop Loss: $${optimalStop.toFixed(2)}`);
|
||||
}
|
||||
|
||||
// Example 4: Integrated Risk Management in Trading
|
||||
async function integratedTradingExample() {
|
||||
console.log('\n=== Integrated Risk Management Example ===');
|
||||
|
||||
const engine = new TradingEngine('backtest', {
|
||||
startTime: Date.now() - 30 * 24 * 60 * 60 * 1000, // 30 days ago
|
||||
endTime: Date.now(),
|
||||
speedMultiplier: 1
|
||||
});
|
||||
|
||||
const riskAnalyzer = new RiskAnalyzer(100000, 0.02, 252);
|
||||
const obAnalyzer = new OrderbookAnalyzer();
|
||||
|
||||
// Simulate a trading decision
|
||||
const symbol = 'AAPL';
|
||||
|
||||
// 1. Check orderbook liquidity
|
||||
engine.updateQuote(symbol, 149.95, 150.05, 5000, 5500);
|
||||
const snapshot = engine.getOrderbookSnapshot(symbol, 10);
|
||||
const analytics = JSON.parse(obAnalyzer.analyzeOrderbook(snapshot));
|
||||
|
||||
console.log('Pre-trade Analysis:');
|
||||
console.log(` Liquidity Score: ${analytics.liquidity_score.toFixed(2)}`);
|
||||
console.log(` Spread: ${analytics.spread_bps.toFixed(1)} bps`);
|
||||
console.log(` Orderbook Imbalance: ${(analytics.imbalance * 100).toFixed(1)}%`);
|
||||
|
||||
// 2. Calculate position size based on current conditions
|
||||
const positionSizeJson = riskAnalyzer.calculatePositionSize(
|
||||
0.7, // signal_strength
|
||||
0.8, // signal_confidence
|
||||
0.018, // volatility
|
||||
analytics.liquidity_score, // from orderbook
|
||||
0.05, // current_drawdown
|
||||
150, // price
|
||||
147, // stop_loss
|
||||
'trending'
|
||||
);
|
||||
|
||||
const positionSize = JSON.parse(positionSizeJson);
|
||||
console.log(`\nPosition Sizing:`);
|
||||
console.log(` Recommended Shares: ${positionSize.shares}`);
|
||||
console.log(` Risk-Adjusted Size: ${(positionSize.risk_adjusted_size * 100).toFixed(2)}%`);
|
||||
|
||||
// 3. Check market impact before placing order
|
||||
const orderValue = positionSize.shares * 150;
|
||||
const impact = JSON.parse(obAnalyzer.calculateMarketImpact(snapshot, orderValue, true));
|
||||
|
||||
console.log(`\nExpected Market Impact:`);
|
||||
console.log(` Price Impact: ${(impact.price_impact * 100).toFixed(3)}%`);
|
||||
console.log(` Expected Fill Price: $${impact.avg_execution_price.toFixed(2)}`);
|
||||
|
||||
// 4. Risk check
|
||||
const riskCheck = engine.checkRisk({
|
||||
id: '123',
|
||||
symbol: symbol,
|
||||
side: 'buy',
|
||||
quantity: positionSize.shares,
|
||||
orderType: 'market',
|
||||
timeInForce: 'DAY'
|
||||
});
|
||||
|
||||
const riskResult = JSON.parse(riskCheck);
|
||||
console.log(`\nRisk Check: ${riskResult.passed ? 'PASSED' : 'FAILED'}`);
|
||||
if (!riskResult.passed) {
|
||||
console.log(' Violations:', riskResult.violations);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to generate random returns
|
||||
function generateRandomReturns(length: number): number[] {
|
||||
const returns: number[] = [];
|
||||
for (let i = 0; i < length; i++) {
|
||||
// Generate returns with mean 0.0005 (0.05%) and std dev 0.02 (2%)
|
||||
const return_ = (Math.random() - 0.5) * 0.04 + 0.0005;
|
||||
returns.push(return_);
|
||||
}
|
||||
return returns;
|
||||
}
|
||||
|
||||
// Run all examples
|
||||
async function runExamples() {
|
||||
try {
|
||||
await orderbookAnalyticsExample();
|
||||
await portfolioRiskExample();
|
||||
await betSizingExample();
|
||||
await integratedTradingExample();
|
||||
} catch (error) {
|
||||
console.error('Error running examples:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute if running directly
|
||||
if (require.main === module) {
|
||||
runExamples();
|
||||
}
|
||||
115
apps/stock/orchestrator/examples/simple-backtest-test.ts
Normal file
115
apps/stock/orchestrator/examples/simple-backtest-test.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
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';
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('BacktestTest');
|
||||
|
||||
async function runSimpleBacktest() {
|
||||
// Create service container
|
||||
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-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 - shorter period for faster testing
|
||||
const config = {
|
||||
mode: 'backtest' as const, // Add mode field
|
||||
name: 'SMA Crossover Test',
|
||||
strategy: 'sma-crossover',
|
||||
symbols: ['AAPL'], // Just one symbol for simplicity
|
||||
startDate: '2023-01-01T00:00:00Z', // ISO datetime format
|
||||
endDate: '2024-01-01T00:00:00Z', // ISO datetime format
|
||||
initialCapital: 100000,
|
||||
commission: 0.001,
|
||||
slippage: 0.0001,
|
||||
dataFrequency: '1d' as const,
|
||||
speed: 'max' as const
|
||||
};
|
||||
|
||||
console.log('Starting backtest with configuration:', config);
|
||||
|
||||
try {
|
||||
const result = await backtestEngine.runBacktest(config);
|
||||
|
||||
console.log('\n=== 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)}%`);
|
||||
console.log(`Sharpe Ratio: ${result.metrics.sharpeRatio.toFixed(2)}`);
|
||||
console.log(`Max Drawdown: ${result.metrics.maxDrawdown.toFixed(2)}%`);
|
||||
|
||||
console.log('\n=== TRADE HISTORY ===');
|
||||
if (result.trades.length === 0) {
|
||||
console.log('No trades were executed!');
|
||||
} else {
|
||||
result.trades.forEach((trade, i) => {
|
||||
console.log(`\nTrade ${i + 1}:`);
|
||||
console.log(` Symbol: ${trade.symbol}`);
|
||||
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)}%)`);
|
||||
});
|
||||
}
|
||||
|
||||
// Show some equity curve data
|
||||
console.log('\n=== EQUITY CURVE (first and last 5 points) ===');
|
||||
const equityCurve = result.equity;
|
||||
if (equityCurve.length > 0) {
|
||||
equityCurve.slice(0, 5).forEach(point => {
|
||||
console.log(`${point.date}: $${point.value.toFixed(2)}`);
|
||||
});
|
||||
if (equityCurve.length > 10) {
|
||||
console.log('...');
|
||||
equityCurve.slice(-5).forEach(point => {
|
||||
console.log(`${point.date}: $${point.value.toFixed(2)}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Backtest failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test
|
||||
runSimpleBacktest().catch(console.error);
|
||||
84
apps/stock/orchestrator/examples/test-backtest-trades.ts
Normal file
84
apps/stock/orchestrator/examples/test-backtest-trades.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
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';
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('BacktestTest');
|
||||
|
||||
async function runBacktestWithDetailedLogging() {
|
||||
// Create service container
|
||||
const container: IServiceContainer = {
|
||||
logger,
|
||||
custom: {}
|
||||
};
|
||||
|
||||
// Initialize services
|
||||
const storageService = new StorageService();
|
||||
const marketDataService = new MarketDataService(container);
|
||||
const executionService = new ExecutionService(container);
|
||||
const modeManager = new ModeManager(container);
|
||||
const strategyManager = new StrategyManager(container);
|
||||
|
||||
// Set services in container
|
||||
container.custom = {
|
||||
MarketDataService: marketDataService,
|
||||
ExecutionService: executionService,
|
||||
ModeManager: modeManager,
|
||||
StorageService: storageService
|
||||
};
|
||||
|
||||
// Set backtest mode
|
||||
await modeManager.setMode('backtest', {
|
||||
startTime: new Date('2020-01-01').getTime(),
|
||||
endTime: new Date('2025-01-01').getTime(),
|
||||
speedMultiplier: 1
|
||||
});
|
||||
|
||||
// Create backtest engine
|
||||
const backtestEngine = new BacktestEngine(container, storageService, strategyManager);
|
||||
|
||||
// Configure backtest
|
||||
const config = {
|
||||
name: 'SMA Crossover Test',
|
||||
strategy: 'sma-crossover',
|
||||
symbols: ['AAPL', 'GOOGL', 'MSFT'],
|
||||
startDate: '2020-01-01',
|
||||
endDate: '2025-01-01',
|
||||
initialCapital: 100000,
|
||||
commission: 0.001,
|
||||
slippage: 0.0001,
|
||||
dataFrequency: '1d'
|
||||
};
|
||||
|
||||
logger.info('Starting backtest with configuration:', config);
|
||||
|
||||
try {
|
||||
const result = await backtestEngine.runBacktest(config);
|
||||
|
||||
logger.info('=== BACKTEST RESULTS ===');
|
||||
logger.info(`Total Trades: ${result.metrics.totalTrades}`);
|
||||
logger.info(`Win Rate: ${result.metrics.winRate.toFixed(2)}%`);
|
||||
logger.info(`Total Return: ${result.metrics.totalReturn.toFixed(2)}%`);
|
||||
logger.info(`Sharpe Ratio: ${result.metrics.sharpeRatio.toFixed(2)}`);
|
||||
logger.info(`Max Drawdown: ${result.metrics.maxDrawdown.toFixed(2)}%`);
|
||||
|
||||
logger.info('\n=== TRADE HISTORY ===');
|
||||
result.trades.forEach((trade, i) => {
|
||||
logger.info(`Trade ${i + 1}:`);
|
||||
logger.info(` Symbol: ${trade.symbol}`);
|
||||
logger.info(` Entry: ${trade.entryDate} @ $${trade.entryPrice.toFixed(2)}`);
|
||||
logger.info(` Exit: ${trade.exitDate} @ $${trade.exitPrice.toFixed(2)}`);
|
||||
logger.info(` P&L: $${trade.pnl.toFixed(2)} (${trade.pnlPercent.toFixed(2)}%)`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Backtest failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test
|
||||
runBacktestWithDetailedLogging().catch(console.error);
|
||||
Loading…
Add table
Add a link
Reference in a new issue