messy work. backtests / mock-data
This commit is contained in:
parent
4e4a048988
commit
fa70ada2bb
51 changed files with 2576 additions and 887 deletions
174
apps/stock/web-api/src/services/backtest.service.ts
Normal file
174
apps/stock/web-api/src/services/backtest.service.ts
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('backtest-service');
|
||||
|
||||
// Use environment variable or default
|
||||
const ORCHESTRATOR_URL = process.env.ORCHESTRATOR_URL || 'http://localhost:2004';
|
||||
|
||||
export interface BacktestRequest {
|
||||
strategy: string;
|
||||
symbols: string[];
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
initialCapital: number;
|
||||
config?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface BacktestJob {
|
||||
id: string;
|
||||
status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
||||
strategy: string;
|
||||
symbols: string[];
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
initialCapital: number;
|
||||
config: Record<string, any>;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// In-memory storage for demo (replace with database)
|
||||
const backtestStore = new Map<string, BacktestJob>();
|
||||
const backtestResults = new Map<string, any>();
|
||||
|
||||
export class BacktestService {
|
||||
async createBacktest(request: BacktestRequest): Promise<BacktestJob> {
|
||||
const backtestId = uuidv4();
|
||||
|
||||
// Store in memory (replace with database)
|
||||
const backtest: BacktestJob = {
|
||||
id: backtestId,
|
||||
status: 'pending',
|
||||
strategy: request.strategy,
|
||||
symbols: request.symbols,
|
||||
startDate: new Date(request.startDate),
|
||||
endDate: new Date(request.endDate),
|
||||
initialCapital: request.initialCapital,
|
||||
config: request.config || {},
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
backtestStore.set(backtestId, backtest);
|
||||
|
||||
// Call orchestrator to run backtest
|
||||
try {
|
||||
const response = await fetch(`${ORCHESTRATOR_URL}/api/backtest/run`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
mode: 'backtest',
|
||||
startDate: new Date(request.startDate).toISOString(),
|
||||
endDate: new Date(request.endDate).toISOString(),
|
||||
symbols: request.symbols,
|
||||
initialCapital: request.initialCapital,
|
||||
dataFrequency: '1d', // Default to daily
|
||||
speed: 'max', // Default speed
|
||||
fillModel: {
|
||||
slippage: 'realistic',
|
||||
marketImpact: true,
|
||||
partialFills: true
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Orchestrator returned ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// Store result when available
|
||||
if (result.performance) {
|
||||
// Backtest completed immediately
|
||||
backtest.status = 'completed';
|
||||
backtestStore.set(backtestId, backtest);
|
||||
backtestResults.set(backtestId, result);
|
||||
} else {
|
||||
// Update status to running if not completed
|
||||
backtest.status = 'running';
|
||||
backtestStore.set(backtestId, backtest);
|
||||
}
|
||||
|
||||
logger.info('Backtest started in orchestrator', { backtestId, result });
|
||||
} catch (error) {
|
||||
logger.error('Failed to start backtest in orchestrator', { backtestId, error });
|
||||
backtest.status = 'failed';
|
||||
backtest.error = error.message;
|
||||
}
|
||||
|
||||
return backtest;
|
||||
}
|
||||
|
||||
async getBacktest(id: string): Promise<BacktestJob | null> {
|
||||
return backtestStore.get(id) || null;
|
||||
}
|
||||
|
||||
async getBacktestResults(id: string): Promise<any> {
|
||||
const results = backtestResults.get(id);
|
||||
if (!results) return null;
|
||||
|
||||
// Transform orchestrator response to frontend expected format
|
||||
return {
|
||||
backtestId: results.id || id,
|
||||
metrics: {
|
||||
totalReturn: results.performance?.totalReturn || 0,
|
||||
sharpeRatio: results.performance?.sharpeRatio || 0,
|
||||
maxDrawdown: results.performance?.maxDrawdown || 0,
|
||||
winRate: results.performance?.winRate || 0,
|
||||
totalTrades: results.performance?.totalTrades || 0,
|
||||
profitFactor: results.performance?.profitFactor
|
||||
},
|
||||
equity: results.equityCurve?.map((point: any) => ({
|
||||
date: new Date(point.timestamp).toISOString(),
|
||||
value: point.value
|
||||
})) || [],
|
||||
trades: results.trades?.map((trade: any) => ({
|
||||
symbol: trade.symbol,
|
||||
entryDate: new Date(trade.entryTime).toISOString(),
|
||||
exitDate: new Date(trade.exitTime).toISOString(),
|
||||
entryPrice: trade.entryPrice,
|
||||
exitPrice: trade.exitPrice,
|
||||
quantity: trade.quantity,
|
||||
pnl: trade.pnl
|
||||
})) || [],
|
||||
ohlcData: results.ohlcData || {}
|
||||
};
|
||||
}
|
||||
|
||||
async listBacktests(params: { limit: number; offset: number }): Promise<BacktestJob[]> {
|
||||
const all = Array.from(backtestStore.values());
|
||||
return all
|
||||
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
|
||||
.slice(params.offset, params.offset + params.limit);
|
||||
}
|
||||
|
||||
async updateBacktestStatus(id: string, status: BacktestJob['status'], error?: string): Promise<void> {
|
||||
const backtest = backtestStore.get(id);
|
||||
if (backtest) {
|
||||
backtest.status = status;
|
||||
backtest.updatedAt = new Date();
|
||||
if (error) {
|
||||
backtest.error = error;
|
||||
}
|
||||
backtestStore.set(id, backtest);
|
||||
}
|
||||
}
|
||||
|
||||
async cancelBacktest(id: string): Promise<void> {
|
||||
await this.updateBacktestStatus(id, 'cancelled');
|
||||
|
||||
// Call orchestrator to stop backtest
|
||||
try {
|
||||
await fetch(`${ORCHESTRATOR_URL}/api/backtest/stop`, {
|
||||
method: 'POST',
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to stop backtest in orchestrator', { backtestId: id, error });
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue