+
+ )
+}
+
+export default Recommendations
diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js
index d0ea938..4a63832 100644
--- a/frontend/src/services/api.js
+++ b/frontend/src/services/api.js
@@ -120,4 +120,81 @@ export const api = {
}
return response.json();
},
+
+ // 交易推荐
+ getRecommendations: async (params = {}) => {
+ const query = new URLSearchParams(params).toString();
+ const url = query ? `${buildUrl('/api/recommendations')}?${query}` : buildUrl('/api/recommendations');
+ const response = await fetch(url);
+ if (!response.ok) {
+ const error = await response.json().catch(() => ({ detail: '获取推荐失败' }));
+ throw new Error(error.detail || '获取推荐失败');
+ }
+ return response.json();
+ },
+
+ getActiveRecommendations: async () => {
+ const response = await fetch(buildUrl('/api/recommendations/active'));
+ if (!response.ok) {
+ const error = await response.json().catch(() => ({ detail: '获取有效推荐失败' }));
+ throw new Error(error.detail || '获取有效推荐失败');
+ }
+ return response.json();
+ },
+
+ getRecommendation: async (id) => {
+ const response = await fetch(buildUrl(`/api/recommendations/${id}`));
+ if (!response.ok) {
+ const error = await response.json().catch(() => ({ detail: '获取推荐详情失败' }));
+ throw new Error(error.detail || '获取推荐详情失败');
+ }
+ return response.json();
+ },
+
+ generateRecommendations: async (minSignalStrength = 5, maxRecommendations = 20) => {
+ const response = await fetch(
+ buildUrl(`/api/recommendations/generate?min_signal_strength=${minSignalStrength}&max_recommendations=${maxRecommendations}`),
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ }
+ );
+ if (!response.ok) {
+ const error = await response.json().catch(() => ({ detail: '生成推荐失败' }));
+ throw new Error(error.detail || '生成推荐失败');
+ }
+ return response.json();
+ },
+
+ markRecommendationExecuted: async (id, tradeId = null) => {
+ const response = await fetch(buildUrl(`/api/recommendations/${id}/execute`), {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ trade_id: tradeId }),
+ });
+ if (!response.ok) {
+ const error = await response.json().catch(() => ({ detail: '标记执行失败' }));
+ throw new Error(error.detail || '标记执行失败');
+ }
+ return response.json();
+ },
+
+ cancelRecommendation: async (id, notes = null) => {
+ const response = await fetch(buildUrl(`/api/recommendations/${id}/cancel`), {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ notes }),
+ });
+ if (!response.ok) {
+ const error = await response.json().catch(() => ({ detail: '取消推荐失败' }));
+ throw new Error(error.detail || '取消推荐失败');
+ }
+ return response.json();
+ },
};
+
+
+ 交易推荐
+
+
+
+
+
+
+
+
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+ {loading ? (
+ 加载中...
+ ) : recommendations.length === 0 ? (
+
+
+ ) : (
+ 暂无推荐记录
+ +
+ {recommendations.map((rec) => (
+
+ )}
+
+
+ ))}
+
+
+
+
+ {rec.symbol}
+
+ {rec.direction === 'BUY' ? '做多' : '做空'}
+
+ {getStatusBadge(rec.status)}
+
+
+
+ 信号强度: {rec.signal_strength}/10
+
+ {formatTime(rec.recommendation_time)}
+
+
+
+
+
+
+
+
+ {parseFloat(rec.current_price || 0).toFixed(4)} USDT
+
+ {rec.change_percent && (
+
+
+ = 0 ? 'positive' : 'negative'}>
+ {rec.change_percent >= 0 ? '+' : ''}{parseFloat(rec.change_percent).toFixed(2)}%
+
+
+ )}
+
+ 推荐原因:
+
+
+ {rec.recommendation_reason || '-'}
+
+
+
+
+
+ {showDetails[rec.id] && (
+
+
+ {parseFloat(rec.suggested_stop_loss || 0).toFixed(4)}
+
+
+
+ {parseFloat(rec.suggested_take_profit_1 || 0).toFixed(4)}
+
+
+
+ {parseFloat(rec.suggested_take_profit_2 || 0).toFixed(4)}
+
+
+
+ {(parseFloat(rec.suggested_position_percent || 0) * 100).toFixed(2)}%
+
+
+
+ {rec.suggested_leverage || 10}x
+
+
+
+ )}
+
+ {rec.status === 'active' && (
+
+
+
+ 技术指标
+
+ {rec.rsi && (
+
+
+
+ {parseFloat(rec.rsi).toFixed(2)}
+
+ )}
+ {rec.macd_histogram !== null && rec.macd_histogram !== undefined && (
+
+
+ {parseFloat(rec.macd_histogram).toFixed(6)}
+
+ )}
+ {rec.ema20 && (
+
+
+ {parseFloat(rec.ema20).toFixed(4)}
+
+ )}
+ {rec.ema50 && (
+
+
+ {parseFloat(rec.ema50).toFixed(4)}
+
+ )}
+ {rec.ema20_4h && (
+
+
+ {parseFloat(rec.ema20_4h).toFixed(4)}
+
+ )}
+ {rec.atr && (
+
+
+ {parseFloat(rec.atr).toFixed(4)}
+
+ )}
+
+
+
+ {rec.bollinger_upper && (
+ 市场状态
+
+
+
+
+ {rec.market_regime === 'trending' ? '趋势' : rec.market_regime === 'ranging' ? '震荡' : '-'}
+
+
+
+
+ {rec.trend_4h === 'up' ? '向上' : rec.trend_4h === 'down' ? '向下' : rec.trend_4h === 'neutral' ? '中性' : '-'}
+
+
+ {rec.volume_24h && (
+
+
+ {(parseFloat(rec.volume_24h) / 1000000).toFixed(2)}M USDT
+
+ )}
+
+
+ )}
+ 布林带
+
+
+
+
+ {parseFloat(rec.bollinger_upper).toFixed(4)}
+
+
+
+ {parseFloat(rec.bollinger_middle).toFixed(4)}
+
+
+
+ {parseFloat(rec.bollinger_lower).toFixed(4)}
+
+
+
+
+
+ )}
+