238 lines
6.6 KiB
TypeScript
238 lines
6.6 KiB
TypeScript
import { HttpClient } from '@angular/common/http';
|
|
import { Injectable } from '@angular/core';
|
|
import { Observable } from 'rxjs';
|
|
|
|
export interface TradingStrategy {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
status: 'ACTIVE' | 'INACTIVE' | 'PAUSED' | 'ERROR';
|
|
type: string;
|
|
symbols: string[];
|
|
parameters: Record<string, any>;
|
|
performance: {
|
|
totalTrades: number;
|
|
winRate: number;
|
|
totalReturn: number;
|
|
sharpeRatio: number;
|
|
maxDrawdown: number;
|
|
};
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
export interface BacktestRequest {
|
|
strategyType: string;
|
|
strategyParams: Record<string, any>;
|
|
symbols: string[];
|
|
startDate: Date | string;
|
|
endDate: Date | string;
|
|
initialCapital: number;
|
|
dataResolution: '1m' | '5m' | '15m' | '30m' | '1h' | '4h' | '1d';
|
|
commission: number;
|
|
slippage: number;
|
|
mode: 'event' | 'vector';
|
|
}
|
|
|
|
export interface BacktestResult {
|
|
strategyId: string;
|
|
startDate: Date;
|
|
endDate: Date;
|
|
duration: number;
|
|
initialCapital: number;
|
|
finalCapital: number;
|
|
totalReturn: number;
|
|
annualizedReturn: number;
|
|
sharpeRatio: number;
|
|
maxDrawdown: number;
|
|
maxDrawdownDuration: number;
|
|
winRate: number;
|
|
totalTrades: number;
|
|
winningTrades: number;
|
|
losingTrades: number;
|
|
averageWinningTrade: number;
|
|
averageLosingTrade: number;
|
|
profitFactor: number;
|
|
dailyReturns: Array<{ date: Date; return: number }>;
|
|
trades: Array<{
|
|
symbol: string;
|
|
entryTime: Date;
|
|
entryPrice: number;
|
|
exitTime: Date;
|
|
exitPrice: number;
|
|
quantity: number;
|
|
pnl: number;
|
|
pnlPercent: number;
|
|
}>;
|
|
// Advanced metrics
|
|
sortinoRatio?: number;
|
|
calmarRatio?: number;
|
|
omegaRatio?: number;
|
|
cagr?: number;
|
|
volatility?: number;
|
|
ulcerIndex?: number;
|
|
}
|
|
|
|
interface ApiResponse<T> {
|
|
success: boolean;
|
|
data: T;
|
|
error?: string;
|
|
}
|
|
|
|
@Injectable({
|
|
providedIn: 'root',
|
|
})
|
|
export class StrategyService {
|
|
private apiBaseUrl = '/api'; // Will be proxied to the correct backend endpoint
|
|
|
|
constructor(private http: HttpClient) {}
|
|
|
|
// Strategy Management
|
|
getStrategies(): Observable<ApiResponse<TradingStrategy[]>> {
|
|
return this.http.get<ApiResponse<TradingStrategy[]>>(`${this.apiBaseUrl}/strategies`);
|
|
}
|
|
|
|
getStrategy(id: string): Observable<ApiResponse<TradingStrategy>> {
|
|
return this.http.get<ApiResponse<TradingStrategy>>(`${this.apiBaseUrl}/strategies/${id}`);
|
|
}
|
|
|
|
createStrategy(strategy: Partial<TradingStrategy>): Observable<ApiResponse<TradingStrategy>> {
|
|
return this.http.post<ApiResponse<TradingStrategy>>(`${this.apiBaseUrl}/strategies`, strategy);
|
|
}
|
|
|
|
updateStrategy(
|
|
id: string,
|
|
updates: Partial<TradingStrategy>
|
|
): Observable<ApiResponse<TradingStrategy>> {
|
|
return this.http.put<ApiResponse<TradingStrategy>>(
|
|
`${this.apiBaseUrl}/strategies/${id}`,
|
|
updates
|
|
);
|
|
}
|
|
|
|
startStrategy(id: string): Observable<ApiResponse<TradingStrategy>> {
|
|
return this.http.post<ApiResponse<TradingStrategy>>(
|
|
`${this.apiBaseUrl}/strategies/${id}/start`,
|
|
{}
|
|
);
|
|
}
|
|
|
|
stopStrategy(id: string): Observable<ApiResponse<TradingStrategy>> {
|
|
return this.http.post<ApiResponse<TradingStrategy>>(
|
|
`${this.apiBaseUrl}/strategies/${id}/stop`,
|
|
{}
|
|
);
|
|
}
|
|
|
|
pauseStrategy(id: string): Observable<ApiResponse<TradingStrategy>> {
|
|
return this.http.post<ApiResponse<TradingStrategy>>(
|
|
`${this.apiBaseUrl}/strategies/${id}/pause`,
|
|
{}
|
|
);
|
|
}
|
|
|
|
// Backtest Management
|
|
getStrategyTypes(): Observable<ApiResponse<string[]>> {
|
|
return this.http.get<ApiResponse<string[]>>(`${this.apiBaseUrl}/strategy-types`);
|
|
}
|
|
|
|
getStrategyParameters(type: string): Observable<ApiResponse<Record<string, any>>> {
|
|
return this.http.get<ApiResponse<Record<string, any>>>(
|
|
`${this.apiBaseUrl}/strategy-parameters/${type}`
|
|
);
|
|
}
|
|
|
|
runBacktest(request: BacktestRequest): Observable<ApiResponse<BacktestResult>> {
|
|
return this.http.post<ApiResponse<BacktestResult>>(`${this.apiBaseUrl}/backtest`, request);
|
|
}
|
|
getBacktestResult(id: string): Observable<ApiResponse<BacktestResult>> {
|
|
return this.http.get<ApiResponse<BacktestResult>>(`${this.apiBaseUrl}/backtest/${id}`);
|
|
}
|
|
|
|
optimizeStrategy(
|
|
baseRequest: BacktestRequest,
|
|
parameterGrid: Record<string, any[]>
|
|
): Observable<ApiResponse<Array<BacktestResult & { parameters: Record<string, any> }>>> {
|
|
return this.http.post<ApiResponse<Array<BacktestResult & { parameters: Record<string, any> }>>>(
|
|
`${this.apiBaseUrl}/backtest/optimize`,
|
|
{ baseRequest, parameterGrid }
|
|
);
|
|
}
|
|
|
|
// Strategy Signals and Trades
|
|
getStrategySignals(strategyId: string): Observable<
|
|
ApiResponse<
|
|
Array<{
|
|
id: string;
|
|
strategyId: string;
|
|
symbol: string;
|
|
action: string;
|
|
price: number;
|
|
quantity: number;
|
|
timestamp: Date;
|
|
confidence: number;
|
|
metadata?: any;
|
|
}>
|
|
>
|
|
> {
|
|
return this.http.get<ApiResponse<any[]>>(`${this.apiBaseUrl}/strategies/${strategyId}/signals`);
|
|
}
|
|
|
|
getStrategyTrades(strategyId: string): Observable<
|
|
ApiResponse<
|
|
Array<{
|
|
id: string;
|
|
strategyId: string;
|
|
symbol: string;
|
|
entryPrice: number;
|
|
entryTime: Date;
|
|
exitPrice: number;
|
|
exitTime: Date;
|
|
quantity: number;
|
|
pnl: number;
|
|
pnlPercent: number;
|
|
}>
|
|
>
|
|
> {
|
|
return this.http.get<ApiResponse<any[]>>(`${this.apiBaseUrl}/strategies/${strategyId}/trades`);
|
|
}
|
|
|
|
// Helper methods for common transformations
|
|
formatBacktestRequest(formData: any): BacktestRequest {
|
|
// Handle date formatting and parameter conversion
|
|
return {
|
|
...formData,
|
|
startDate:
|
|
formData.startDate instanceof Date ? formData.startDate.toISOString() : formData.startDate,
|
|
endDate: formData.endDate instanceof Date ? formData.endDate.toISOString() : formData.endDate,
|
|
strategyParams: this.convertParameterTypes(formData.strategyType, formData.strategyParams),
|
|
};
|
|
}
|
|
|
|
private convertParameterTypes(
|
|
strategyType: string,
|
|
params: Record<string, any>
|
|
): Record<string, any> {
|
|
// Convert string parameters to correct types based on strategy requirements
|
|
const result: Record<string, any> = {};
|
|
|
|
for (const [key, value] of Object.entries(params)) {
|
|
if (typeof value === 'string') {
|
|
// Try to convert to number if it looks like a number
|
|
if (!isNaN(Number(value))) {
|
|
result[key] = Number(value);
|
|
} else if (value.toLowerCase() === 'true') {
|
|
result[key] = true;
|
|
} else if (value.toLowerCase() === 'false') {
|
|
result[key] = false;
|
|
} else {
|
|
result[key] = value;
|
|
}
|
|
} else {
|
|
result[key] = value;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|