This commit is contained in:
薇薇安 2026-01-17 22:43:37 +08:00
parent 39e6a46bd3
commit 7336843fec
5 changed files with 354 additions and 9 deletions

View File

@ -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})")

View 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;

View File

@ -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,

View File

@ -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 {

View File

@ -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)}