a
This commit is contained in:
parent
39e6a46bd3
commit
7336843fec
|
|
@ -637,7 +637,13 @@ async def bookmark_recommendation(recommendation_data: dict):
|
||||||
suggested_limit_price=recommendation_data.get('suggested_limit_price'),
|
suggested_limit_price=recommendation_data.get('suggested_limit_price'),
|
||||||
volume_24h=recommendation_data.get('volume_24h'),
|
volume_24h=recommendation_data.get('volume_24h'),
|
||||||
volatility=recommendation_data.get('volatility'),
|
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})")
|
logger.info(f"✓ 推荐已标记到数据库: {recommendation_data.get('symbol')} {recommendation_data.get('direction')} (ID: {recommendation_id})")
|
||||||
|
|
|
||||||
104
backend/database/add_user_guide_to_recommendations.sql
Normal file
104
backend/database/add_user_guide_to_recommendations.sql
Normal file
|
|
@ -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;
|
||||||
|
|
@ -377,7 +377,9 @@ class TradeRecommendation:
|
||||||
suggested_stop_loss=None, suggested_take_profit_1=None, suggested_take_profit_2=None,
|
suggested_stop_loss=None, suggested_take_profit_1=None, suggested_take_profit_2=None,
|
||||||
suggested_position_percent=None, suggested_leverage=10,
|
suggested_position_percent=None, suggested_leverage=10,
|
||||||
order_type='LIMIT', suggested_limit_price=None,
|
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()
|
recommendation_time = get_beijing_time()
|
||||||
|
|
@ -391,7 +393,41 @@ class TradeRecommendation:
|
||||||
except:
|
except:
|
||||||
has_order_fields = False
|
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(
|
db.execute_update(
|
||||||
"""INSERT INTO trade_recommendations
|
"""INSERT INTO trade_recommendations
|
||||||
(symbol, direction, recommendation_time, current_price, change_percent,
|
(symbol, direction, recommendation_time, current_price, change_percent,
|
||||||
|
|
|
||||||
|
|
@ -300,12 +300,32 @@
|
||||||
grid-column: span 2;
|
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 {
|
.limit-price-value {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.limit-price-value.highlight-price {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #1976d2;
|
||||||
|
}
|
||||||
|
|
||||||
.price-diff {
|
.price-diff {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
|
@ -375,6 +395,137 @@
|
||||||
font-weight: 500;
|
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) {
|
@media (max-width: 768px) {
|
||||||
.recommendations-viewer {
|
.recommendations-viewer {
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,22 @@ function RecommendationsViewer() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card-content">
|
<div className="card-content">
|
||||||
|
{/* 推荐分类和风险等级标签 */}
|
||||||
|
{(rec.recommendation_category || rec.risk_level) && (
|
||||||
|
<div className="recommendation-tags">
|
||||||
|
{rec.recommendation_category && (
|
||||||
|
<span className={`category-tag category-${rec.recommendation_category?.replace(/[(\(\))]/g, '').replace(/\s+/g, '-').toLowerCase() || 'default'}`}>
|
||||||
|
{rec.recommendation_category}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{rec.risk_level && (
|
||||||
|
<span className={`risk-tag risk-${rec.risk_level?.replace(/\s+/g, '-').toLowerCase() || 'medium'}`}>
|
||||||
|
风险: {rec.risk_level}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="price-info">
|
<div className="price-info">
|
||||||
<div className="price-item">
|
<div className="price-item">
|
||||||
<label>当前价格:</label>
|
<label>当前价格:</label>
|
||||||
|
|
@ -213,22 +229,38 @@ function RecommendationsViewer() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 用户指南(人话版计划) */}
|
||||||
|
{rec.user_guide && (
|
||||||
|
<div className="user-guide">
|
||||||
|
<strong>📋 操作计划:</strong>
|
||||||
|
<pre className="user-guide-content">{rec.user_guide}</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 交易教程(如果存在) */}
|
||||||
|
{rec.trading_tutorial && (
|
||||||
|
<div className="trading-tutorial">
|
||||||
|
<strong>💡 交易提示:</strong>
|
||||||
|
<p>{rec.trading_tutorial}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="recommendation-reason">
|
<div className="recommendation-reason">
|
||||||
<strong>推荐原因:</strong>
|
<strong>技术分析原因:</strong>
|
||||||
<p>{rec.recommendation_reason || '-'}</p>
|
<p>{rec.recommendation_reason || '-'}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="suggested-params">
|
<div className="suggested-params">
|
||||||
<div className="param-item order-type">
|
<div className="param-item order-type">
|
||||||
<label>订单类型:</label>
|
<label>订单类型:</label>
|
||||||
<span className={`order-type-badge ${(rec.order_type || 'LIMIT').toLowerCase()}`}>
|
<span className="order-type-badge limit">
|
||||||
{rec.order_type === 'LIMIT' ? '限价单' : '市价单'}
|
限价单
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{rec.order_type === 'LIMIT' && rec.suggested_limit_price && (
|
{rec.suggested_limit_price && (
|
||||||
<div className="param-item limit-price">
|
<div className="param-item limit-price highlight">
|
||||||
<label>建议挂单价:</label>
|
<label>建议挂单价:</label>
|
||||||
<span className="limit-price-value">
|
<span className="limit-price-value highlight-price">
|
||||||
{parseFloat(rec.suggested_limit_price || 0).toFixed(4)} USDT
|
{parseFloat(rec.suggested_limit_price || 0).toFixed(4)} USDT
|
||||||
{rec.current_price && (
|
{rec.current_price && (
|
||||||
<span className="price-diff">
|
<span className="price-diff">
|
||||||
|
|
@ -261,6 +293,22 @@ function RecommendationsViewer() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 预期持仓时间(如果存在) */}
|
||||||
|
{rec.expected_hold_time && (
|
||||||
|
<div className="expected-hold-time">
|
||||||
|
<strong>⏱️ 预期持仓时间:</strong>
|
||||||
|
<span>{rec.expected_hold_time}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 最大持仓天数提醒 */}
|
||||||
|
{rec.max_hold_days && (
|
||||||
|
<div className="max-hold-warning">
|
||||||
|
<strong>⚠️ 退出提醒:</strong>
|
||||||
|
<span>若持仓超过{rec.max_hold_days}天仍未触及第一目标,建议平仓离场重新评估</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="btn-toggle-details"
|
className="btn-toggle-details"
|
||||||
onClick={() => toggleDetails(key)}
|
onClick={() => toggleDetails(key)}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user