React frontend with: - Authentication UI - Trading dashboard - ML signals display - Portfolio management Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
533 lines
13 KiB
TypeScript
533 lines
13 KiB
TypeScript
/**
|
|
* LLM Agent Service
|
|
* Client for connecting to the LLM Agent API (Predictions & Backtesting)
|
|
*
|
|
* @version 1.0.0
|
|
*/
|
|
|
|
const LLM_AGENT_URL = import.meta.env.VITE_LLM_AGENT_URL || 'http://localhost:3085';
|
|
|
|
// ============================================================================
|
|
// Types - Predictions
|
|
// ============================================================================
|
|
|
|
export interface PredictionRequest {
|
|
symbol: string;
|
|
timeframe?: string;
|
|
}
|
|
|
|
export interface AMDPhaseInfo {
|
|
phase: string;
|
|
confidence: number;
|
|
support: number;
|
|
resistance: number;
|
|
}
|
|
|
|
export interface SignalInfo {
|
|
direction: 'LONG' | 'SHORT' | 'HOLD';
|
|
confidence: number;
|
|
entry_price: number;
|
|
stop_loss: number;
|
|
take_profit: number;
|
|
}
|
|
|
|
export interface RangePredictionInfo {
|
|
predicted_high: number;
|
|
predicted_low: number;
|
|
expected_range_pct: number;
|
|
}
|
|
|
|
export interface ICTContextInfo {
|
|
market_bias: string;
|
|
order_blocks: Array<{ type: string; price: number }>;
|
|
fair_value_gaps: Array<{ type: string; high: number; low: number }>;
|
|
}
|
|
|
|
export interface PredictionResponse {
|
|
symbol: string;
|
|
timeframe: string;
|
|
timestamp: string;
|
|
current_price: number;
|
|
amd_phase: AMDPhaseInfo;
|
|
signal: SignalInfo;
|
|
range_prediction: RangePredictionInfo;
|
|
ict_context: ICTContextInfo;
|
|
confluence_score: number;
|
|
explanation: string;
|
|
risk_assessment?: {
|
|
allowed: boolean;
|
|
recommended_size: number;
|
|
position_risk_pct: number;
|
|
checks: Record<string, boolean>;
|
|
};
|
|
}
|
|
|
|
export interface ActiveSignal {
|
|
symbol: string;
|
|
direction: string;
|
|
confidence: number;
|
|
confluence_score: number;
|
|
entry_price: number;
|
|
stop_loss: number;
|
|
take_profit: number;
|
|
timestamp: string;
|
|
}
|
|
|
|
export interface RiskSummary {
|
|
level: string;
|
|
limits: {
|
|
max_position_size_pct: number;
|
|
max_daily_drawdown_pct: number;
|
|
max_total_exposure_pct: number;
|
|
};
|
|
current_state: {
|
|
daily_pnl: number;
|
|
total_exposure_pct: number;
|
|
open_trades: number;
|
|
};
|
|
circuit_breaker_active: boolean;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Types - Backtesting
|
|
// ============================================================================
|
|
|
|
export interface BacktestRequest {
|
|
initial_balance: number;
|
|
risk_per_trade_pct: number;
|
|
max_open_trades: number;
|
|
min_confluence_score: number;
|
|
min_confidence: number;
|
|
symbols: string[];
|
|
timeframe: string;
|
|
days_back: number;
|
|
use_amd_filter: boolean;
|
|
use_killzone_filter: boolean;
|
|
}
|
|
|
|
export interface BacktestStatus {
|
|
id: string;
|
|
status: 'pending' | 'running' | 'completed' | 'failed';
|
|
progress: number;
|
|
started_at?: string;
|
|
completed_at?: string;
|
|
error?: string;
|
|
}
|
|
|
|
export interface BacktestSummary {
|
|
id: string;
|
|
initial_balance: number;
|
|
final_balance: number;
|
|
total_return: number;
|
|
total_return_pct: number;
|
|
total_trades: number;
|
|
win_rate: number;
|
|
profit_factor: number;
|
|
max_drawdown_pct: number;
|
|
sharpe_ratio: number;
|
|
duration_days: number;
|
|
}
|
|
|
|
export interface BacktestTrade {
|
|
id: string;
|
|
symbol: string;
|
|
direction: string;
|
|
entry_price: number;
|
|
exit_price: number;
|
|
entry_time: string;
|
|
exit_time: string;
|
|
pnl: number;
|
|
pnl_pips: number;
|
|
status: string;
|
|
confidence: number;
|
|
amd_phase: string;
|
|
confluence_score: number;
|
|
}
|
|
|
|
export interface EquityCurvePoint {
|
|
timestamp: string;
|
|
equity: number;
|
|
balance: number;
|
|
drawdown_pct: number;
|
|
}
|
|
|
|
export interface BacktestResult {
|
|
summary: {
|
|
initial_balance: number;
|
|
final_balance: number;
|
|
total_return: number;
|
|
total_return_pct: number;
|
|
duration_days: number;
|
|
};
|
|
performance: {
|
|
total_trades: number;
|
|
winning_trades: number;
|
|
losing_trades: number;
|
|
win_rate: number;
|
|
profit_factor: number;
|
|
sharpe_ratio: number;
|
|
sortino_ratio: number;
|
|
};
|
|
risk: {
|
|
max_drawdown: number;
|
|
max_drawdown_pct: number;
|
|
avg_winner: number;
|
|
avg_loser: number;
|
|
largest_winner: number;
|
|
largest_loser: number;
|
|
};
|
|
activity: {
|
|
avg_trade_pnl: number;
|
|
avg_trade_duration_hours: number;
|
|
trades_per_day: number;
|
|
};
|
|
monthly_returns: Record<string, number>;
|
|
trades_by_phase: Record<string, { count: number; wins: number; pnl: number; win_rate: number }>;
|
|
trades_by_symbol: Record<string, { count: number; wins: number; pnl: number; win_rate: number }>;
|
|
equity_curve: EquityCurvePoint[];
|
|
trades: BacktestTrade[];
|
|
}
|
|
|
|
// ============================================================================
|
|
// Predictions API
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Analyze a symbol with all ML models
|
|
*/
|
|
export async function analyzeSymbol(
|
|
symbol: string,
|
|
timeframe: string = '5m'
|
|
): Promise<PredictionResponse | null> {
|
|
try {
|
|
const response = await fetch(`${LLM_AGENT_URL}/api/v1/predictions/analyze`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ symbol, timeframe }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`API error: ${response.status}`);
|
|
}
|
|
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error('Error analyzing symbol:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get active signals above minimum confluence
|
|
*/
|
|
export async function getActiveSignals(
|
|
minConfluence: number = 0.6
|
|
): Promise<ActiveSignal[]> {
|
|
try {
|
|
const response = await fetch(
|
|
`${LLM_AGENT_URL}/api/v1/predictions/active-signals?min_confluence=${minConfluence}`
|
|
);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`API error: ${response.status}`);
|
|
}
|
|
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error('Error fetching active signals:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get current risk summary
|
|
*/
|
|
export async function getRiskSummary(): Promise<RiskSummary | null> {
|
|
try {
|
|
const response = await fetch(`${LLM_AGENT_URL}/api/v1/predictions/risk-summary`);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`API error: ${response.status}`);
|
|
}
|
|
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error('Error fetching risk summary:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set risk level
|
|
*/
|
|
export async function setRiskLevel(
|
|
level: 'minimal' | 'conservative' | 'moderate' | 'aggressive'
|
|
): Promise<boolean> {
|
|
try {
|
|
const response = await fetch(
|
|
`${LLM_AGENT_URL}/api/v1/predictions/risk-level?level=${level}`,
|
|
{ method: 'POST' }
|
|
);
|
|
|
|
return response.ok;
|
|
} catch (error) {
|
|
console.error('Error setting risk level:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate a trade against risk rules
|
|
*/
|
|
export async function validateTrade(params: {
|
|
symbol: string;
|
|
side: 'BUY' | 'SELL';
|
|
size: number;
|
|
entry_price: number;
|
|
stop_loss: number;
|
|
pip_value?: number;
|
|
}): Promise<{
|
|
allowed: boolean;
|
|
recommended_size: number;
|
|
position_risk_pct: number;
|
|
checks: Record<string, boolean>;
|
|
formatted_message: string;
|
|
} | null> {
|
|
try {
|
|
const response = await fetch(`${LLM_AGENT_URL}/api/v1/predictions/validate-trade`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(params),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`API error: ${response.status}`);
|
|
}
|
|
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error('Error validating trade:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Backtesting API
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Start a new backtest (background)
|
|
*/
|
|
export async function startBacktest(
|
|
params: Partial<BacktestRequest>
|
|
): Promise<BacktestStatus | null> {
|
|
try {
|
|
const request: BacktestRequest = {
|
|
initial_balance: params.initial_balance || 1000,
|
|
risk_per_trade_pct: params.risk_per_trade_pct || 1.0,
|
|
max_open_trades: params.max_open_trades || 3,
|
|
min_confluence_score: params.min_confluence_score || 0.65,
|
|
min_confidence: params.min_confidence || 0.60,
|
|
symbols: params.symbols || ['XAUUSD', 'EURUSD', 'GBPUSD'],
|
|
timeframe: params.timeframe || '1H',
|
|
days_back: params.days_back || 365,
|
|
use_amd_filter: params.use_amd_filter ?? true,
|
|
use_killzone_filter: params.use_killzone_filter ?? true,
|
|
};
|
|
|
|
const response = await fetch(`${LLM_AGENT_URL}/api/v1/backtesting/run`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(request),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`API error: ${response.status}`);
|
|
}
|
|
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error('Error starting backtest:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run a quick synchronous backtest (< 90 days)
|
|
*/
|
|
export async function runQuickBacktest(params: {
|
|
initial_balance?: number;
|
|
days?: number;
|
|
symbol?: string;
|
|
}): Promise<BacktestResult | null> {
|
|
try {
|
|
const queryParams = new URLSearchParams({
|
|
initial_balance: String(params.initial_balance || 1000),
|
|
days: String(params.days || 30),
|
|
symbol: params.symbol || 'XAUUSD',
|
|
});
|
|
|
|
const response = await fetch(
|
|
`${LLM_AGENT_URL}/api/v1/backtesting/quick-test?${queryParams}`
|
|
);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`API error: ${response.status}`);
|
|
}
|
|
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error('Error running quick backtest:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get backtest status
|
|
*/
|
|
export async function getBacktestStatus(id: string): Promise<BacktestStatus | null> {
|
|
try {
|
|
const response = await fetch(`${LLM_AGENT_URL}/api/v1/backtesting/status/${id}`);
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 404) return null;
|
|
throw new Error(`API error: ${response.status}`);
|
|
}
|
|
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error('Error fetching backtest status:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get backtest results
|
|
*/
|
|
export async function getBacktestResults(id: string): Promise<BacktestResult | null> {
|
|
try {
|
|
const response = await fetch(`${LLM_AGENT_URL}/api/v1/backtesting/results/${id}`);
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 404) return null;
|
|
throw new Error(`API error: ${response.status}`);
|
|
}
|
|
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error('Error fetching backtest results:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* List recent backtests
|
|
*/
|
|
export async function listBacktests(limit: number = 10): Promise<BacktestSummary[]> {
|
|
try {
|
|
const response = await fetch(
|
|
`${LLM_AGENT_URL}/api/v1/backtesting/list?limit=${limit}`
|
|
);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`API error: ${response.status}`);
|
|
}
|
|
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error('Error listing backtests:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete a backtest
|
|
*/
|
|
export async function deleteBacktest(id: string): Promise<boolean> {
|
|
try {
|
|
const response = await fetch(`${LLM_AGENT_URL}/api/v1/backtesting/${id}`, {
|
|
method: 'DELETE',
|
|
});
|
|
|
|
return response.ok;
|
|
} catch (error) {
|
|
console.error('Error deleting backtest:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Health Check
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Check LLM Agent health
|
|
*/
|
|
export async function checkHealth(): Promise<{
|
|
llmAgent: boolean;
|
|
predictions: boolean;
|
|
backtesting: boolean;
|
|
}> {
|
|
const results = {
|
|
llmAgent: false,
|
|
predictions: false,
|
|
backtesting: false,
|
|
};
|
|
|
|
try {
|
|
const [rootRes, predictionsRes, backtestingRes] = await Promise.allSettled([
|
|
fetch(`${LLM_AGENT_URL}/`),
|
|
fetch(`${LLM_AGENT_URL}/api/v1/predictions/health`),
|
|
fetch(`${LLM_AGENT_URL}/api/v1/backtesting/health`),
|
|
]);
|
|
|
|
results.llmAgent = rootRes.status === 'fulfilled' && rootRes.value.ok;
|
|
results.predictions = predictionsRes.status === 'fulfilled' && predictionsRes.value.ok;
|
|
results.backtesting = backtestingRes.status === 'fulfilled' && backtestingRes.value.ok;
|
|
} catch (error) {
|
|
console.error('Error checking LLM Agent health:', error);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Polling utilities
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Poll for backtest completion
|
|
*/
|
|
export async function waitForBacktest(
|
|
id: string,
|
|
onProgress?: (progress: number) => void,
|
|
pollInterval: number = 2000,
|
|
timeout: number = 600000 // 10 minutes
|
|
): Promise<BacktestResult | null> {
|
|
const startTime = Date.now();
|
|
|
|
while (Date.now() - startTime < timeout) {
|
|
const status = await getBacktestStatus(id);
|
|
|
|
if (!status) {
|
|
throw new Error('Backtest not found');
|
|
}
|
|
|
|
if (onProgress) {
|
|
onProgress(status.progress);
|
|
}
|
|
|
|
if (status.status === 'completed') {
|
|
return await getBacktestResults(id);
|
|
}
|
|
|
|
if (status.status === 'failed') {
|
|
throw new Error(status.error || 'Backtest failed');
|
|
}
|
|
|
|
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
}
|
|
|
|
throw new Error('Backtest timed out');
|
|
}
|