diff --git a/backend/api/routes/recommendations.py b/backend/api/routes/recommendations.py index 1cf14d4..8ca10ec 100644 --- a/backend/api/routes/recommendations.py +++ b/backend/api/routes/recommendations.py @@ -637,7 +637,13 @@ async def bookmark_recommendation(recommendation_data: dict): suggested_limit_price=recommendation_data.get('suggested_limit_price'), volume_24h=recommendation_data.get('volume_24h'), volatility=recommendation_data.get('volatility'), - notes=recommendation_data.get('notes', '用户标记用于复盘') + notes=recommendation_data.get('notes', '用户标记用于复盘'), + user_guide=recommendation_data.get('user_guide'), + recommendation_category=recommendation_data.get('recommendation_category'), + risk_level=recommendation_data.get('risk_level'), + expected_hold_time=recommendation_data.get('expected_hold_time'), + trading_tutorial=recommendation_data.get('trading_tutorial'), + max_hold_days=recommendation_data.get('max_hold_days', 3) ) logger.info(f"✓ 推荐已标记到数据库: {recommendation_data.get('symbol')} {recommendation_data.get('direction')} (ID: {recommendation_id})") diff --git a/backend/database/add_user_guide_to_recommendations.sql b/backend/database/add_user_guide_to_recommendations.sql new file mode 100644 index 0000000..3cd8697 --- /dev/null +++ b/backend/database/add_user_guide_to_recommendations.sql @@ -0,0 +1,104 @@ +-- 为 trade_recommendations 表添加 user_guide 字段 +-- 用于存储用户指南(人话版计划) + +-- 检查字段是否存在,如果不存在则添加 +SET @dbname = DATABASE(); +SET @tablename = 'trade_recommendations'; +SET @columnname = 'user_guide'; +SET @preparedStatement = (SELECT IF( + ( + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS + WHERE + (TABLE_SCHEMA = @dbname) + AND (TABLE_NAME = @tablename) + AND (COLUMN_NAME = @columnname) + ) > 0, + "SELECT 'Column already exists.' AS result;", + CONCAT("ALTER TABLE ", @tablename, " ADD COLUMN ", @columnname, " TEXT COMMENT '用户指南(人话版操作计划)';") +)); +PREPARE alterIfNotExists FROM @preparedStatement; +EXECUTE alterIfNotExists; +DEALLOCATE PREPARE alterIfNotExists; + +-- 同时添加其他新增字段(如果不存在) +SET @columnname2 = 'recommendation_category'; +SET @preparedStatement2 = (SELECT IF( + ( + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS + WHERE + (TABLE_SCHEMA = @dbname) + AND (TABLE_NAME = @tablename) + AND (COLUMN_NAME = @columnname2) + ) > 0, + "SELECT 'Column recommendation_category already exists.' AS result;", + CONCAT("ALTER TABLE ", @tablename, " ADD COLUMN ", @columnname2, " VARCHAR(50) COMMENT '推荐分类(如:顺趋势突破、逆势反弹等)';") +)); +PREPARE alterIfNotExists2 FROM @preparedStatement2; +EXECUTE alterIfNotExists2; +DEALLOCATE PREPARE alterIfNotExists2; + +SET @columnname3 = 'risk_level'; +SET @preparedStatement3 = (SELECT IF( + ( + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS + WHERE + (TABLE_SCHEMA = @dbname) + AND (TABLE_NAME = @tablename) + AND (COLUMN_NAME = @columnname3) + ) > 0, + "SELECT 'Column risk_level already exists.' AS result;", + CONCAT("ALTER TABLE ", @tablename, " ADD COLUMN ", @columnname3, " VARCHAR(20) COMMENT '风险等级(高、中高、中等、低中)';") +)); +PREPARE alterIfNotExists3 FROM @preparedStatement3; +EXECUTE alterIfNotExists3; +DEALLOCATE PREPARE alterIfNotExists3; + +SET @columnname4 = 'expected_hold_time'; +SET @preparedStatement4 = (SELECT IF( + ( + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS + WHERE + (TABLE_SCHEMA = @dbname) + AND (TABLE_NAME = @tablename) + AND (COLUMN_NAME = @columnname4) + ) > 0, + "SELECT 'Column expected_hold_time already exists.' AS result;", + CONCAT("ALTER TABLE ", @tablename, " ADD COLUMN ", @columnname4, " VARCHAR(100) COMMENT '预期持仓时间';") +)); +PREPARE alterIfNotExists4 FROM @preparedStatement4; +EXECUTE alterIfNotExists4; +DEALLOCATE PREPARE alterIfNotExists4; + +SET @columnname5 = 'trading_tutorial'; +SET @preparedStatement5 = (SELECT IF( + ( + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS + WHERE + (TABLE_SCHEMA = @dbname) + AND (TABLE_NAME = @tablename) + AND (COLUMN_NAME = @columnname5) + ) > 0, + "SELECT 'Column trading_tutorial already exists.' AS result;", + CONCAT("ALTER TABLE ", @tablename, " ADD COLUMN ", @columnname5, " TEXT COMMENT '交易教程提示';") +)); +PREPARE alterIfNotExists5 FROM @preparedStatement5; +EXECUTE alterIfNotExists5; +DEALLOCATE PREPARE alterIfNotExists5; + +SET @columnname6 = 'max_hold_days'; +SET @preparedStatement6 = (SELECT IF( + ( + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS + WHERE + (TABLE_SCHEMA = @dbname) + AND (TABLE_NAME = @tablename) + AND (COLUMN_NAME = @columnname6) + ) > 0, + "SELECT 'Column max_hold_days already exists.' AS result;", + CONCAT("ALTER TABLE ", @tablename, " ADD COLUMN ", @columnname6, " INT DEFAULT 3 COMMENT '最大持仓天数';") +)); +PREPARE alterIfNotExists6 FROM @preparedStatement6; +EXECUTE alterIfNotExists6; +DEALLOCATE PREPARE alterIfNotExists6; + +SELECT 'Migration completed: Added user_guide and related fields to trade_recommendations table.' AS result; diff --git a/backend/database/models.py b/backend/database/models.py index afc5e55..5ac9d81 100644 --- a/backend/database/models.py +++ b/backend/database/models.py @@ -377,7 +377,9 @@ class TradeRecommendation: suggested_stop_loss=None, suggested_take_profit_1=None, suggested_take_profit_2=None, suggested_position_percent=None, suggested_leverage=10, order_type='LIMIT', suggested_limit_price=None, - volume_24h=None, volatility=None, notes=None + volume_24h=None, volatility=None, notes=None, + user_guide=None, recommendation_category=None, risk_level=None, + expected_hold_time=None, trading_tutorial=None, max_hold_days=3 ): """创建推荐记录(使用北京时间)""" recommendation_time = get_beijing_time() @@ -391,7 +393,41 @@ class TradeRecommendation: except: has_order_fields = False - if has_order_fields: + # 检查是否有 user_guide 字段 + try: + db.execute_one("SELECT user_guide FROM trade_recommendations LIMIT 1") + has_user_guide_fields = True + except: + has_user_guide_fields = False + + if has_user_guide_fields: + # 包含所有新字段(user_guide等) + db.execute_update( + """INSERT INTO trade_recommendations + (symbol, direction, recommendation_time, current_price, change_percent, + recommendation_reason, signal_strength, market_regime, trend_4h, + rsi, macd_histogram, bollinger_upper, bollinger_middle, bollinger_lower, + ema20, ema50, ema20_4h, atr, + suggested_stop_loss, suggested_take_profit_1, suggested_take_profit_2, + suggested_position_percent, suggested_leverage, + order_type, suggested_limit_price, + volume_24h, volatility, expires_at, notes, + user_guide, recommendation_category, risk_level, + expected_hold_time, trading_tutorial, max_hold_days) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", + (symbol, direction, recommendation_time, current_price, change_percent, + recommendation_reason, signal_strength, market_regime, trend_4h, + rsi, macd_histogram, bollinger_upper, bollinger_middle, bollinger_lower, + ema20, ema50, ema20_4h, atr, + suggested_stop_loss, suggested_take_profit_1, suggested_take_profit_2, + suggested_position_percent, suggested_leverage, + order_type, suggested_limit_price, + volume_24h, volatility, expires_at, notes, + user_guide, recommendation_category, risk_level, + expected_hold_time, trading_tutorial, max_hold_days) + ) + elif has_order_fields: + # 只有 order_type 字段,没有 user_guide 字段 db.execute_update( """INSERT INTO trade_recommendations (symbol, direction, recommendation_time, current_price, change_percent, diff --git a/recommendations-viewer/src/components/RecommendationsViewer.css b/recommendations-viewer/src/components/RecommendationsViewer.css index 39da01c..1b92bb2 100644 --- a/recommendations-viewer/src/components/RecommendationsViewer.css +++ b/recommendations-viewer/src/components/RecommendationsViewer.css @@ -300,12 +300,32 @@ grid-column: span 2; } +.param-item.limit-price.highlight { + background-color: #e3f2fd; + border: 2px solid #2196F3; + border-radius: 6px; + padding: 10px; + margin: 5px 0; +} + +.param-item.limit-price.highlight label { + color: #1976d2; + font-weight: bold; + font-size: 13px; +} + .limit-price-value { display: flex; flex-direction: column; gap: 4px; } +.limit-price-value.highlight-price { + font-size: 18px; + font-weight: bold; + color: #1976d2; +} + .price-diff { font-size: 11px; color: #666; @@ -375,6 +395,137 @@ font-weight: 500; } +/* 推荐分类和风险标签 */ +.recommendation-tags { + display: flex; + gap: 8px; + flex-wrap: wrap; + margin-bottom: 10px; +} + +.category-tag, +.risk-tag { + padding: 4px 10px; + border-radius: 4px; + font-size: 12px; + font-weight: bold; +} + +.category-tag { + background-color: #2196F3; + color: white; +} + +.category-tag.category-逆势反弹高风险 { + background-color: #f44336; +} + +.category-tag.category-震荡区间操作 { + background-color: #FF9800; +} + +.risk-tag { + background-color: #9E9E9E; + color: white; +} + +.risk-tag.risk-高 { + background-color: #f44336; +} + +.risk-tag.risk-中高 { + background-color: #FF5722; +} + +.risk-tag.risk-中等 { + background-color: #FF9800; +} + +.risk-tag.risk-低中 { + background-color: #4CAF50; +} + +/* 用户指南 */ +.user-guide { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 15px; + border-radius: 8px; + margin: 10px 0; +} + +.user-guide strong { + display: block; + margin-bottom: 10px; + font-size: 16px; +} + +.user-guide-content { + margin: 0; + white-space: pre-wrap; + font-family: 'Courier New', monospace; + font-size: 13px; + line-height: 1.8; + background: rgba(255, 255, 255, 0.1); + padding: 12px; + border-radius: 4px; + overflow-x: auto; +} + +/* 交易教程 */ +.trading-tutorial { + background-color: #fff3cd; + border-left: 4px solid #ffc107; + padding: 12px 15px; + border-radius: 4px; + margin: 10px 0; +} + +.trading-tutorial strong { + display: block; + margin-bottom: 8px; + color: #856404; +} + +.trading-tutorial p { + margin: 0; + color: #856404; + line-height: 1.6; +} + +/* 预期持仓时间和退出提醒 */ +.expected-hold-time, +.max-hold-warning { + background-color: #e3f2fd; + border-left: 4px solid #2196F3; + padding: 10px 15px; + border-radius: 4px; + margin: 10px 0; +} + +.expected-hold-time strong, +.max-hold-warning strong { + display: inline-block; + margin-right: 8px; + color: #1976d2; +} + +.expected-hold-time span, +.max-hold-warning span { + color: #1976d2; + line-height: 1.6; +} + +.max-hold-warning { + background-color: #fff3e0; + border-left-color: #ff9800; +} + +.max-hold-warning strong, +.max-hold-warning span { + color: #e65100; +} + /* 响应式设计 */ @media (max-width: 768px) { .recommendations-viewer { diff --git a/recommendations-viewer/src/components/RecommendationsViewer.jsx b/recommendations-viewer/src/components/RecommendationsViewer.jsx index 5e9d726..3041158 100644 --- a/recommendations-viewer/src/components/RecommendationsViewer.jsx +++ b/recommendations-viewer/src/components/RecommendationsViewer.jsx @@ -193,6 +193,22 @@ function RecommendationsViewer() {
+ {/* 推荐分类和风险等级标签 */} + {(rec.recommendation_category || rec.risk_level) && ( +
+ {rec.recommendation_category && ( + + {rec.recommendation_category} + + )} + {rec.risk_level && ( + + 风险: {rec.risk_level} + + )} +
+ )} +
@@ -213,22 +229,38 @@ function RecommendationsViewer() { )}
+ {/* 用户指南(人话版计划) */} + {rec.user_guide && ( +
+ 📋 操作计划: +
{rec.user_guide}
+
+ )} + + {/* 交易教程(如果存在) */} + {rec.trading_tutorial && ( +
+ 💡 交易提示: +

{rec.trading_tutorial}

+
+ )} +
- 推荐原因: + 技术分析原因:

{rec.recommendation_reason || '-'}

- - {rec.order_type === 'LIMIT' ? '限价单' : '市价单'} + + 限价单
- {rec.order_type === 'LIMIT' && rec.suggested_limit_price && ( -
+ {rec.suggested_limit_price && ( +
- + {parseFloat(rec.suggested_limit_price || 0).toFixed(4)} USDT {rec.current_price && ( @@ -261,6 +293,22 @@ function RecommendationsViewer() {
+ {/* 预期持仓时间(如果存在) */} + {rec.expected_hold_time && ( +
+ ⏱️ 预期持仓时间: + {rec.expected_hold_time} +
+ )} + + {/* 最大持仓天数提醒 */} + {rec.max_hold_days && ( +
+ ⚠️ 退出提醒: + 若持仓超过{rec.max_hold_days}天仍未触及第一目标,建议平仓离场重新评估 +
+ )} +