This commit is contained in:
薇薇安 2026-01-15 11:34:53 +08:00
parent 2539ebc300
commit b08d97b442
5 changed files with 769 additions and 1 deletions

View File

@ -3,7 +3,7 @@ FastAPI应用主入口
"""
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from api.routes import config, trades, stats, dashboard, account
from api.routes import config, trades, stats, dashboard, account, recommendations
import os
import logging
from pathlib import Path
@ -118,6 +118,7 @@ app.include_router(trades.router, prefix="/api/trades", tags=["交易记录"])
app.include_router(stats.router, prefix="/api/stats", tags=["统计分析"])
app.include_router(dashboard.router, prefix="/api/dashboard", tags=["仪表板"])
app.include_router(account.router, prefix="/api/account", tags=["账户数据"])
app.include_router(recommendations.router, tags=["交易推荐"])
@app.get("/")

View File

@ -0,0 +1,263 @@
"""
推荐交易对API路由
"""
from fastapi import APIRouter, HTTPException, Query
from typing import Optional, List
from datetime import datetime, timedelta
from database.models import TradeRecommendation
import logging
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/recommendations", tags=["recommendations"])
@router.get("")
async def get_recommendations(
status: Optional[str] = Query(None, description="状态过滤: active, executed, expired, cancelled"),
direction: Optional[str] = Query(None, description="方向过滤: BUY, SELL"),
limit: int = Query(50, ge=1, le=200, description="返回数量限制"),
start_date: Optional[str] = Query(None, description="开始日期 (YYYY-MM-DD)"),
end_date: Optional[str] = Query(None, description="结束日期 (YYYY-MM-DD)")
):
"""
获取推荐交易对列表
Args:
status: 状态过滤
direction: 方向过滤
limit: 返回数量限制
start_date: 开始日期
end_date: 结束日期
"""
try:
# 转换日期字符串
start_dt = None
end_dt = None
if start_date:
try:
start_dt = datetime.strptime(start_date, "%Y-%m-%d")
except ValueError:
raise HTTPException(status_code=400, detail="开始日期格式错误,应为 YYYY-MM-DD")
if end_date:
try:
end_dt = datetime.strptime(end_date, "%Y-%m-%d")
# 设置为当天的23:59:59
end_dt = end_dt.replace(hour=23, minute=59, second=59)
except ValueError:
raise HTTPException(status_code=400, detail="结束日期格式错误,应为 YYYY-MM-DD")
recommendations = TradeRecommendation.get_all(
status=status,
direction=direction,
limit=limit,
start_date=start_dt,
end_date=end_dt
)
return {
"success": True,
"count": len(recommendations),
"data": recommendations
}
except Exception as e:
logger.error(f"获取推荐列表失败: {e}")
raise HTTPException(status_code=500, detail=f"获取推荐列表失败: {str(e)}")
@router.get("/active")
async def get_active_recommendations():
"""
获取当前有效的推荐未过期未执行未取消
"""
try:
recommendations = TradeRecommendation.get_active()
return {
"success": True,
"count": len(recommendations),
"data": recommendations
}
except Exception as e:
logger.error(f"获取有效推荐失败: {e}")
raise HTTPException(status_code=500, detail=f"获取有效推荐失败: {str(e)}")
@router.get("/{recommendation_id}")
async def get_recommendation(recommendation_id: int):
"""
根据ID获取推荐详情
"""
try:
recommendation = TradeRecommendation.get_by_id(recommendation_id)
if not recommendation:
raise HTTPException(status_code=404, detail="推荐不存在")
return {
"success": True,
"data": recommendation
}
except HTTPException:
raise
except Exception as e:
logger.error(f"获取推荐详情失败: {e}")
raise HTTPException(status_code=500, detail=f"获取推荐详情失败: {str(e)}")
@router.get("/symbol/{symbol}")
async def get_recommendations_by_symbol(
symbol: str,
limit: int = Query(10, ge=1, le=50, description="返回数量限制")
):
"""
根据交易对获取推荐记录
"""
try:
recommendations = TradeRecommendation.get_by_symbol(symbol, limit=limit)
return {
"success": True,
"count": len(recommendations),
"data": recommendations
}
except Exception as e:
logger.error(f"获取交易对推荐失败: {e}")
raise HTTPException(status_code=500, detail=f"获取交易对推荐失败: {str(e)}")
@router.post("/{recommendation_id}/execute")
async def mark_recommendation_executed(
recommendation_id: int,
trade_id: Optional[int] = None
):
"""
标记推荐已执行
Args:
recommendation_id: 推荐ID
trade_id: 关联的交易记录ID可选
"""
try:
recommendation = TradeRecommendation.get_by_id(recommendation_id)
if not recommendation:
raise HTTPException(status_code=404, detail="推荐不存在")
if recommendation['status'] != 'active':
raise HTTPException(
status_code=400,
detail=f"推荐状态为 {recommendation['status']},无法标记为已执行"
)
TradeRecommendation.mark_executed(recommendation_id, trade_id)
return {
"success": True,
"message": "推荐已标记为已执行"
}
except HTTPException:
raise
except Exception as e:
logger.error(f"标记推荐已执行失败: {e}")
raise HTTPException(status_code=500, detail=f"标记推荐已执行失败: {str(e)}")
@router.post("/{recommendation_id}/cancel")
async def cancel_recommendation(
recommendation_id: int,
notes: Optional[str] = None
):
"""
取消推荐
Args:
recommendation_id: 推荐ID
notes: 取消原因备注
"""
try:
recommendation = TradeRecommendation.get_by_id(recommendation_id)
if not recommendation:
raise HTTPException(status_code=404, detail="推荐不存在")
if recommendation['status'] != 'active':
raise HTTPException(
status_code=400,
detail=f"推荐状态为 {recommendation['status']},无法取消"
)
TradeRecommendation.mark_cancelled(recommendation_id, notes)
return {
"success": True,
"message": "推荐已取消"
}
except HTTPException:
raise
except Exception as e:
logger.error(f"取消推荐失败: {e}")
raise HTTPException(status_code=500, detail=f"取消推荐失败: {str(e)}")
@router.post("/generate")
async def generate_recommendations(
min_signal_strength: int = Query(5, ge=0, le=10, description="最小信号强度"),
max_recommendations: int = Query(20, ge=1, le=50, description="最大推荐数量")
):
"""
生成新的交易推荐
Args:
min_signal_strength: 最小信号强度
max_recommendations: 最大推荐数量
"""
try:
# 导入推荐器
import sys
from pathlib import Path
project_root = Path(__file__).parent.parent.parent
trading_system_path = project_root / 'trading_system'
if not trading_system_path.exists():
raise HTTPException(status_code=500, detail="交易系统模块不存在")
sys.path.insert(0, str(trading_system_path))
from binance_client import BinanceClient
from market_scanner import MarketScanner
from risk_manager import RiskManager
from trade_recommender import TradeRecommender
import config
# 初始化组件
client = BinanceClient(
api_key=config.BINANCE_API_KEY,
api_secret=config.BINANCE_API_SECRET,
testnet=config.USE_TESTNET
)
await client.connect()
try:
scanner = MarketScanner(client)
risk_manager = RiskManager(client)
recommender = TradeRecommender(client, scanner, risk_manager)
# 生成推荐
recommendations = await recommender.generate_recommendations(
min_signal_strength=min_signal_strength,
max_recommendations=max_recommendations
)
return {
"success": True,
"count": len(recommendations),
"message": f"成功生成 {len(recommendations)} 个推荐",
"data": recommendations
}
finally:
await client.disconnect()
except HTTPException:
raise
except Exception as e:
logger.error(f"生成推荐失败: {e}")
import traceback
logger.error(traceback.format_exc())
raise HTTPException(status_code=500, detail=f"生成推荐失败: {str(e)}")

View File

@ -80,6 +80,51 @@ CREATE TABLE IF NOT EXISTS `trading_signals` (
INDEX `idx_executed` (`executed`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='交易信号表';
-- 推荐交易对表
CREATE TABLE IF NOT EXISTS `trade_recommendations` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`symbol` VARCHAR(20) NOT NULL,
`direction` VARCHAR(10) NOT NULL COMMENT 'BUY, SELL',
`recommendation_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`current_price` DECIMAL(20, 8) NOT NULL,
`change_percent` DECIMAL(10, 4) COMMENT '24小时涨跌幅',
`recommendation_reason` TEXT NOT NULL COMMENT '推荐原因',
`signal_strength` INT NOT NULL COMMENT '信号强度 0-10',
`market_regime` VARCHAR(20) COMMENT 'trending, ranging',
`trend_4h` VARCHAR(10) COMMENT '4H周期趋势: up, down, neutral',
-- 技术指标
`rsi` DECIMAL(10, 4) COMMENT 'RSI指标',
`macd_histogram` DECIMAL(20, 8) COMMENT 'MACD柱状图',
`bollinger_upper` DECIMAL(20, 8) COMMENT '布林带上轨',
`bollinger_middle` DECIMAL(20, 8) COMMENT '布林带中轨',
`bollinger_lower` DECIMAL(20, 8) COMMENT '布林带下轨',
`ema20` DECIMAL(20, 8) COMMENT 'EMA20',
`ema50` DECIMAL(20, 8) COMMENT 'EMA50',
`ema20_4h` DECIMAL(20, 8) COMMENT '4H周期EMA20',
`atr` DECIMAL(20, 8) COMMENT '平均真实波幅',
-- 建议参数
`suggested_stop_loss` DECIMAL(20, 8) COMMENT '建议止损价',
`suggested_take_profit_1` DECIMAL(20, 8) COMMENT '建议第一目标止盈价盈亏比1:1',
`suggested_take_profit_2` DECIMAL(20, 8) COMMENT '建议第二目标止盈价',
`suggested_position_percent` DECIMAL(10, 4) COMMENT '建议仓位百分比',
`suggested_leverage` INT DEFAULT 10 COMMENT '建议杠杆倍数',
-- 市场数据
`volume_24h` DECIMAL(20, 8) COMMENT '24小时成交量',
`volatility` DECIMAL(10, 4) COMMENT '波动率',
-- 状态
`status` VARCHAR(20) DEFAULT 'active' COMMENT 'active: 有效, expired: 已过期, executed: 已执行, cancelled: 已取消',
`executed_at` TIMESTAMP NULL COMMENT '执行时间',
`execution_result` VARCHAR(50) COMMENT '执行结果: success, failed',
`execution_trade_id` INT COMMENT '关联的交易记录ID',
`expires_at` TIMESTAMP NULL COMMENT '推荐过期时间默认24小时后',
`notes` TEXT COMMENT '备注信息',
INDEX `idx_symbol` (`symbol`),
INDEX `idx_recommendation_time` (`recommendation_time`),
INDEX `idx_status` (`status`),
INDEX `idx_direction` (`direction`),
INDEX `idx_signal_strength` (`signal_strength`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='推荐交易对表';
-- 初始化默认配置

View File

@ -228,3 +228,121 @@ class TradingSignal:
"SELECT * FROM trading_signals ORDER BY signal_time DESC LIMIT %s",
(limit,)
)
class TradeRecommendation:
"""推荐交易对模型"""
@staticmethod
def create(
symbol, direction, current_price, change_percent, recommendation_reason,
signal_strength, market_regime=None, trend_4h=None,
rsi=None, macd_histogram=None,
bollinger_upper=None, bollinger_middle=None, bollinger_lower=None,
ema20=None, ema50=None, ema20_4h=None, atr=None,
suggested_stop_loss=None, suggested_take_profit_1=None, suggested_take_profit_2=None,
suggested_position_percent=None, suggested_leverage=10,
volume_24h=None, volatility=None, notes=None
):
"""创建推荐记录(使用北京时间)"""
recommendation_time = get_beijing_time()
# 默认24小时后过期
expires_at = recommendation_time + timedelta(hours=24)
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,
volume_24h, volatility, expires_at, notes)
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)""",
(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,
volume_24h, volatility, expires_at, notes)
)
return db.execute_one("SELECT LAST_INSERT_ID() as id")['id']
@staticmethod
def mark_executed(recommendation_id, trade_id=None, execution_result='success'):
"""标记推荐已执行"""
executed_at = get_beijing_time()
db.execute_update(
"""UPDATE trade_recommendations
SET status = 'executed', executed_at = %s, execution_result = %s, execution_trade_id = %s
WHERE id = %s""",
(executed_at, execution_result, trade_id, recommendation_id)
)
@staticmethod
def mark_expired(recommendation_id):
"""标记推荐已过期"""
db.execute_update(
"UPDATE trade_recommendations SET status = 'expired' WHERE id = %s",
(recommendation_id,)
)
@staticmethod
def mark_cancelled(recommendation_id, notes=None):
"""标记推荐已取消"""
db.execute_update(
"UPDATE trade_recommendations SET status = 'cancelled', notes = %s WHERE id = %s",
(notes, recommendation_id)
)
@staticmethod
def get_all(status=None, direction=None, limit=100, start_date=None, end_date=None):
"""获取推荐记录"""
query = "SELECT * FROM trade_recommendations WHERE 1=1"
params = []
if status:
query += " AND status = %s"
params.append(status)
if direction:
query += " AND direction = %s"
params.append(direction)
if start_date:
query += " AND recommendation_time >= %s"
params.append(start_date)
if end_date:
query += " AND recommendation_time <= %s"
params.append(end_date)
query += " ORDER BY recommendation_time DESC, signal_strength DESC LIMIT %s"
params.append(limit)
return db.execute_query(query, params)
@staticmethod
def get_active():
"""获取当前有效的推荐(未过期、未执行、未取消)"""
return db.execute_query(
"""SELECT * FROM trade_recommendations
WHERE status = 'active' AND (expires_at IS NULL OR expires_at > NOW())
ORDER BY signal_strength DESC, recommendation_time DESC"""
)
@staticmethod
def get_by_id(recommendation_id):
"""根据ID获取推荐"""
return db.execute_one(
"SELECT * FROM trade_recommendations WHERE id = %s",
(recommendation_id,)
)
@staticmethod
def get_by_symbol(symbol, limit=10):
"""根据交易对获取推荐记录"""
return db.execute_query(
"""SELECT * FROM trade_recommendations
WHERE symbol = %s
ORDER BY recommendation_time DESC LIMIT %s""",
(symbol, limit)
)

View File

@ -0,0 +1,341 @@
"""
推荐交易对模块 - 生成交易推荐供手动参考
"""
import asyncio
import logging
from typing import List, Dict, Optional
from datetime import datetime, timedelta
try:
from .binance_client import BinanceClient
from .market_scanner import MarketScanner
from .risk_manager import RiskManager
from . import config
except ImportError:
from binance_client import BinanceClient
from market_scanner import MarketScanner
from risk_manager import RiskManager
import config
logger = logging.getLogger(__name__)
# 尝试导入数据库模型
DB_AVAILABLE = False
TradeRecommendation = None
try:
import sys
from pathlib import Path
project_root = Path(__file__).parent.parent
backend_path = project_root / 'backend'
if backend_path.exists():
sys.path.insert(0, str(backend_path))
from database.models import TradeRecommendation
DB_AVAILABLE = True
logger.info("✓ 数据库模型导入成功,推荐记录将保存到数据库")
else:
logger.warning("⚠ backend目录不存在无法使用数据库功能")
DB_AVAILABLE = False
except ImportError as e:
logger.warning(f"⚠ 无法导入数据库模型: {e}")
DB_AVAILABLE = False
except Exception as e:
logger.warning(f"⚠ 数据库初始化失败: {e}")
DB_AVAILABLE = False
class TradeRecommender:
"""推荐交易对生成器"""
def __init__(
self,
client: BinanceClient,
scanner: MarketScanner,
risk_manager: RiskManager
):
"""
初始化推荐器
Args:
client: 币安客户端
scanner: 市场扫描器
risk_manager: 风险管理器
"""
self.client = client
self.scanner = scanner
self.risk_manager = risk_manager
async def generate_recommendations(
self,
min_signal_strength: int = 5,
max_recommendations: int = 20
) -> List[Dict]:
"""
生成交易推荐
Args:
min_signal_strength: 最小信号强度默认5低于此强度的不推荐
max_recommendations: 最大推荐数量
Returns:
推荐列表
"""
logger.info("开始生成交易推荐...")
# 1. 扫描市场
top_symbols = await self.scanner.scan_market()
if not top_symbols:
logger.warning("未找到符合条件的交易对")
return []
recommendations = []
# 2. 对每个交易对进行分析
for symbol_info in top_symbols:
if len(recommendations) >= max_recommendations:
break
symbol = symbol_info['symbol']
current_price = symbol_info['price']
change_percent = symbol_info.get('changePercent', 0)
# 3. 分析交易信号(使用策略模块的逻辑)
trade_signal = await self._analyze_trade_signal(symbol_info)
# 4. 如果信号强度足够,生成推荐
if trade_signal['should_trade'] and trade_signal['strength'] >= min_signal_strength:
recommendation = await self._create_recommendation(
symbol_info, trade_signal
)
if recommendation:
recommendations.append(recommendation)
logger.info(f"生成了 {len(recommendations)} 个交易推荐")
return recommendations
async def _analyze_trade_signal(self, symbol_info: Dict) -> Dict:
"""
分析交易信号复用策略模块的逻辑
Args:
symbol_info: 交易对信息
Returns:
交易信号字典
"""
symbol = symbol_info['symbol']
current_price = symbol_info['price']
rsi = symbol_info.get('rsi')
macd = symbol_info.get('macd')
bollinger = symbol_info.get('bollinger')
market_regime = symbol_info.get('marketRegime', 'unknown')
ema20 = symbol_info.get('ema20')
ema50 = symbol_info.get('ema50')
ema20_4h = symbol_info.get('ema20_4h')
price_4h = symbol_info.get('price_4h', current_price)
# 判断4H周期趋势方向
trend_4h = None
if ema20_4h is not None:
if price_4h > ema20_4h:
trend_4h = 'up'
elif price_4h < ema20_4h:
trend_4h = 'down'
else:
trend_4h = 'neutral'
signal_strength = 0
reasons = []
direction = None
# 策略1均值回归震荡市场
if market_regime == 'ranging':
if rsi and rsi < 30:
if trend_4h in ('up', 'neutral', None):
signal_strength += 4
reasons.append(f"RSI超卖({rsi:.1f})")
if direction is None:
direction = 'BUY'
elif rsi and rsi > 70:
if trend_4h in ('down', 'neutral', None):
signal_strength += 4
reasons.append(f"RSI超买({rsi:.1f})")
if direction is None:
direction = 'SELL'
if bollinger and current_price <= bollinger.get('lower'):
if trend_4h in ('up', 'neutral', None):
signal_strength += 3
reasons.append("触及布林带下轨")
if direction is None:
direction = 'BUY'
elif bollinger and current_price >= bollinger.get('upper'):
if trend_4h in ('down', 'neutral', None):
signal_strength += 3
reasons.append("触及布林带上轨")
if direction is None:
direction = 'SELL'
# 策略2趋势跟踪趋势市场
elif market_regime == 'trending':
if macd and macd.get('macd', 0) > macd.get('signal', 0) and macd.get('histogram', 0) > 0:
if trend_4h in ('up', 'neutral', None):
signal_strength += 3
reasons.append("MACD金叉")
if direction is None:
direction = 'BUY'
elif macd and macd.get('macd', 0) < macd.get('signal', 0) and macd.get('histogram', 0) < 0:
if trend_4h in ('down', 'neutral', None):
signal_strength += 3
reasons.append("MACD死叉")
if direction is None:
direction = 'SELL'
if ema20 and ema50:
if current_price > ema20 > ema50:
if trend_4h in ('up', 'neutral', None):
signal_strength += 2
reasons.append("价格在均线之上")
if direction is None:
direction = 'BUY'
elif current_price < ema20 < ema50:
if trend_4h in ('down', 'neutral', None):
signal_strength += 2
reasons.append("价格在均线之下")
if direction is None:
direction = 'SELL'
# 多周期共振加分
if direction and trend_4h:
if (direction == 'BUY' and trend_4h == 'up') or (direction == 'SELL' and trend_4h == 'down'):
signal_strength += 2
reasons.append("4H周期共振确认")
# 判断是否应该交易
min_signal_strength = config.TRADING_CONFIG.get('MIN_SIGNAL_STRENGTH', 7)
should_trade = signal_strength >= min_signal_strength
# 禁止逆4H趋势交易
if direction and trend_4h:
if (direction == 'BUY' and trend_4h == 'down') or (direction == 'SELL' and trend_4h == 'up'):
should_trade = False
reasons.append("❌ 禁止逆4H趋势交易")
return {
'should_trade': should_trade,
'direction': direction,
'reason': ', '.join(reasons) if reasons else '无明确信号',
'strength': signal_strength,
'trend_4h': trend_4h
}
async def _create_recommendation(
self,
symbol_info: Dict,
trade_signal: Dict
) -> Optional[Dict]:
"""
创建推荐记录
Args:
symbol_info: 交易对信息
trade_signal: 交易信号
Returns:
推荐字典
"""
try:
symbol = symbol_info['symbol']
current_price = symbol_info['price']
direction = trade_signal['direction']
# 计算建议的止损止盈
entry_price = current_price
stop_loss_price = self.risk_manager.get_stop_loss_price(
entry_price,
direction,
klines=symbol_info.get('klines'),
bollinger=symbol_info.get('bollinger'),
atr=symbol_info.get('atr')
)
# 计算止损百分比
if direction == 'BUY':
stop_loss_pct = (entry_price - stop_loss_price) / entry_price
else:
stop_loss_pct = (stop_loss_price - entry_price) / entry_price
# 第一目标盈亏比1:1
if direction == 'BUY':
take_profit_1 = entry_price + (entry_price - stop_loss_price)
else:
take_profit_1 = entry_price - (stop_loss_price - entry_price)
# 第二目标止损的2.5倍
take_profit_2_pct = stop_loss_pct * 2.5
take_profit_2 = self.risk_manager.get_take_profit_price(
entry_price, direction, take_profit_pct=take_profit_2_pct
)
# 建议仓位(根据信号强度调整)
base_position_pct = config.TRADING_CONFIG.get('MAX_POSITION_PERCENT', 0.05)
signal_strength = trade_signal['strength']
# 信号强度越高建议仓位可以适当增加但不超过1.5倍)
position_multiplier = min(1.0 + (signal_strength - 5) * 0.1, 1.5)
suggested_position_pct = base_position_pct * position_multiplier
# 准备推荐数据
recommendation_data = {
'symbol': symbol,
'direction': direction,
'current_price': current_price,
'change_percent': symbol_info.get('changePercent', 0),
'recommendation_reason': trade_signal['reason'],
'signal_strength': signal_strength,
'market_regime': symbol_info.get('marketRegime'),
'trend_4h': trade_signal.get('trend_4h'),
'rsi': symbol_info.get('rsi'),
'macd_histogram': symbol_info.get('macd', {}).get('histogram') if symbol_info.get('macd') else None,
'bollinger_upper': symbol_info.get('bollinger', {}).get('upper') if symbol_info.get('bollinger') else None,
'bollinger_middle': symbol_info.get('bollinger', {}).get('middle') if symbol_info.get('bollinger') else None,
'bollinger_lower': symbol_info.get('bollinger', {}).get('lower') if symbol_info.get('bollinger') else None,
'ema20': symbol_info.get('ema20'),
'ema50': symbol_info.get('ema50'),
'ema20_4h': symbol_info.get('ema20_4h'),
'atr': symbol_info.get('atr'),
'suggested_stop_loss': stop_loss_price,
'suggested_take_profit_1': take_profit_1,
'suggested_take_profit_2': take_profit_2,
'suggested_position_percent': suggested_position_pct,
'suggested_leverage': config.TRADING_CONFIG.get('LEVERAGE', 10),
'volume_24h': symbol_info.get('volume24h'),
'volatility': symbol_info.get('volatility')
}
# 保存到数据库
if DB_AVAILABLE and TradeRecommendation:
try:
recommendation_id = TradeRecommendation.create(**recommendation_data)
logger.info(
f"✓ 推荐已保存: {symbol} {direction} "
f"(信号强度: {signal_strength}/10, ID: {recommendation_id})"
)
recommendation_data['id'] = recommendation_id
except Exception as e:
logger.error(f"保存推荐到数据库失败: {e}")
return recommendation_data
except Exception as e:
logger.error(f"创建推荐失败 {symbol_info.get('symbol', 'unknown')}: {e}")
return None
async def get_active_recommendations(self) -> List[Dict]:
"""获取当前有效的推荐"""
if DB_AVAILABLE and TradeRecommendation:
return TradeRecommendation.get_active()
return []
async def mark_recommendation_executed(self, recommendation_id: int, trade_id: int = None):
"""标记推荐已执行"""
if DB_AVAILABLE and TradeRecommendation:
TradeRecommendation.mark_executed(recommendation_id, trade_id)
logger.info(f"推荐 {recommendation_id} 已标记为已执行")