update
This commit is contained in:
parent
c464ae6e4d
commit
92c1a6cbd2
@ -113,7 +113,7 @@ class Settings(BaseSettings):
|
||||
# 模拟交易配置
|
||||
paper_trading_enabled: bool = True # 是否启用模拟交易
|
||||
paper_trading_initial_balance: float = 10000 # 初始本金 (USDT)
|
||||
paper_trading_leverage: int = 10 # 杠杆倍数
|
||||
paper_trading_leverage: int = 20 # 杠杆倍数(全仓模式下的最大杠杆)
|
||||
paper_trading_margin_per_order: float = 1000 # 每单保证金 (USDT)
|
||||
paper_trading_max_orders: int = 10 # 最大持仓+挂单总数
|
||||
paper_trading_auto_close_opposite: bool = False # 是否自动平掉反向持仓(智能策略)
|
||||
|
||||
@ -260,9 +260,16 @@ class CryptoAgent:
|
||||
price_change_24h = self._calculate_price_change(data['1h'])
|
||||
logger.info(f"💰 当前价格: ${current_price:,.2f} ({price_change_24h})")
|
||||
|
||||
# 2. LLM 分析(包含新闻舆情)
|
||||
# 获取当前持仓信息(供 LLM 仓位决策)
|
||||
position_info = self.paper_trading.get_position_info()
|
||||
|
||||
# 2. LLM 分析(包含新闻舆情和持仓信息)
|
||||
logger.info(f"\n🤖 【LLM 分析中...】")
|
||||
result = await self.llm_analyzer.analyze(symbol, data, symbols=self.symbols)
|
||||
result = await self.llm_analyzer.analyze(
|
||||
symbol, data,
|
||||
symbols=self.symbols,
|
||||
position_info=position_info
|
||||
)
|
||||
|
||||
# 输出分析摘要
|
||||
summary = result.get('analysis_summary', '无')
|
||||
@ -345,10 +352,11 @@ class CryptoAgent:
|
||||
# 5. 创建模拟订单
|
||||
if self.paper_trading_enabled and self.paper_trading:
|
||||
grade = best_signal.get('grade', 'D')
|
||||
position_size = best_signal.get('position_size', 'light')
|
||||
if grade != 'D':
|
||||
# 转换信号格式以兼容 paper_trading
|
||||
paper_signal = self._convert_to_paper_signal(symbol, best_signal, current_price)
|
||||
result = self.paper_trading.create_order_from_signal(paper_signal)
|
||||
result = self.paper_trading.create_order_from_signal(paper_signal, current_price)
|
||||
|
||||
# 发送被取消挂单的通知
|
||||
cancelled_orders = result.get('cancelled_orders', [])
|
||||
@ -358,7 +366,7 @@ class CryptoAgent:
|
||||
# 记录新订单
|
||||
order = result.get('order')
|
||||
if order:
|
||||
logger.info(f" 📝 已创建模拟订单: {order.order_id}")
|
||||
logger.info(f" 📝 已创建模拟订单: {order.order_id} | 仓位: {position_size}")
|
||||
else:
|
||||
if best_signal:
|
||||
logger.info(f"\n⏸️ 信号冷却中或置信度不足,不发送通知")
|
||||
@ -389,6 +397,7 @@ class CryptoAgent:
|
||||
'confidence': signal.get('confidence', 0),
|
||||
'signal_grade': signal.get('grade', 'D'),
|
||||
'signal_type': type_map.get(signal_type, 'swing'),
|
||||
'position_size': signal.get('position_size', 'light'), # LLM 建议的仓位大小
|
||||
'reasons': [signal.get('reason', '')],
|
||||
'timestamp': datetime.now()
|
||||
}
|
||||
@ -444,7 +453,14 @@ class CryptoAgent:
|
||||
if not self._validate_data(data):
|
||||
return {'error': '数据不完整'}
|
||||
|
||||
result = await self.llm_analyzer.analyze(symbol, data, symbols=self.symbols)
|
||||
# 获取持仓信息
|
||||
position_info = self.paper_trading.get_position_info()
|
||||
|
||||
result = await self.llm_analyzer.analyze(
|
||||
symbol, data,
|
||||
symbols=self.symbols,
|
||||
position_info=position_info
|
||||
)
|
||||
return result
|
||||
|
||||
def get_status(self) -> Dict[str, Any]:
|
||||
|
||||
@ -112,6 +112,8 @@ class LLMSignalAnalyzer:
|
||||
"entry_type": "market/limit",
|
||||
"confidence": 0-100,
|
||||
"grade": "A/B/C/D",
|
||||
"position_size": "heavy/medium/light",
|
||||
"position_reason": "仓位建议理由(20字以内)",
|
||||
"entry_price": 建议入场价,
|
||||
"stop_loss": 止损价,
|
||||
"take_profit": 止盈价,
|
||||
@ -132,6 +134,27 @@ class LLMSignalAnalyzer:
|
||||
- **C级**(40-59):有机会但量价不够理想
|
||||
- **D级**(<40):量价背离或信号矛盾
|
||||
|
||||
## 七、仓位管理(重要)
|
||||
你需要根据信号质量和当前持仓情况,建议合适的仓位大小。
|
||||
|
||||
### 仓位等级
|
||||
- **heavy**(重仓):机会极佳,建议使用较大仓位
|
||||
- **medium**(中仓):机会不错,建议使用中等仓位
|
||||
- **light**(轻仓):机会一般或风险较高,建议轻仓试探
|
||||
|
||||
### 仓位决策规则
|
||||
1. **A级信号**:可建议 heavy 或 medium
|
||||
2. **B级信号**:建议 medium 或 light
|
||||
3. **C级信号**:只能建议 light
|
||||
4. **已有同向持仓时**:新仓位应降一级(避免过度集中)
|
||||
5. **已有反向持仓时**:谨慎开仓,除非信号极强
|
||||
6. **市场波动剧烈时**:仓位应保守
|
||||
|
||||
### 安全底线(必须遵守)
|
||||
- 总杠杆永远不得超过 20 倍
|
||||
- 单一交易对持仓不宜过大
|
||||
- 如果当前持仓已经较重,即使有好机会也要控制仓位
|
||||
|
||||
## 重要原则
|
||||
1. **量价优先** - 任何信号都必须有量能配合才可靠
|
||||
2. **积极但不冒进** - 有合理依据就给出信号,不要过于保守
|
||||
@ -139,7 +162,8 @@ class LLMSignalAnalyzer:
|
||||
4. 止损必须明确,风险收益比至少 1:1.5
|
||||
5. reason 字段必须包含量价分析(如"放量突破+RSI=45,量比1.8确认有效")
|
||||
6. entry_type 必须明确:信号已触发用 market,等待更好价位用 limit
|
||||
7. 短线信号止损控制在 1-2%,中线信号止损控制在 2-4%"""
|
||||
7. 短线信号止损控制在 1-2%,中线信号止损控制在 2-4%
|
||||
8. **position_size 必须明确**:根据信号质量和持仓情况给出 heavy/medium/light"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化分析器"""
|
||||
@ -150,7 +174,8 @@ class LLMSignalAnalyzer:
|
||||
logger.info(f"LLM 信号分析器初始化完成(含新闻舆情,模型: {self.model_override or '默认'})")
|
||||
|
||||
async def analyze(self, symbol: str, data: Dict[str, pd.DataFrame],
|
||||
symbols: List[str] = None) -> Dict[str, Any]:
|
||||
symbols: List[str] = None,
|
||||
position_info: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
使用 LLM 分析市场数据
|
||||
|
||||
@ -158,6 +183,11 @@ class LLMSignalAnalyzer:
|
||||
symbol: 交易对,如 'BTCUSDT'
|
||||
data: 多周期K线数据 {'5m': df, '15m': df, '1h': df, '4h': df}
|
||||
symbols: 所有监控的交易对(用于过滤相关新闻)
|
||||
position_info: 当前持仓信息,用于仓位管理决策
|
||||
- account_balance: 账户余额
|
||||
- total_position_value: 总持仓价值
|
||||
- current_leverage: 当前杠杆倍数
|
||||
- positions: 各交易对持仓列表
|
||||
|
||||
Returns:
|
||||
分析结果
|
||||
@ -167,7 +197,7 @@ class LLMSignalAnalyzer:
|
||||
news_text = await self._get_news_context(symbol, symbols or [symbol])
|
||||
|
||||
# 构建数据提示
|
||||
data_prompt = self._build_data_prompt(symbol, data, news_text)
|
||||
data_prompt = self._build_data_prompt(symbol, data, news_text, position_info)
|
||||
|
||||
# 调用 LLM
|
||||
response = llm_service.chat([
|
||||
@ -207,8 +237,51 @@ class LLMSignalAnalyzer:
|
||||
# 暂时禁用新闻获取,只做技术面分析
|
||||
return ""
|
||||
|
||||
def _format_position_info(self, symbol: str, position_info: Dict[str, Any]) -> str:
|
||||
"""格式化持仓信息供 LLM 参考"""
|
||||
lines = []
|
||||
|
||||
# 账户概况
|
||||
balance = position_info.get('account_balance', 0)
|
||||
total_value = position_info.get('total_position_value', 0)
|
||||
current_leverage = position_info.get('current_leverage', 0)
|
||||
max_leverage = 20 # 最大杠杆限制
|
||||
|
||||
lines.append(f"- 账户余额: ${balance:,.2f}")
|
||||
lines.append(f"- 总持仓价值: ${total_value:,.2f}")
|
||||
lines.append(f"- 当前杠杆: {current_leverage:.1f}x / {max_leverage}x")
|
||||
|
||||
# 可用杠杆空间
|
||||
available_leverage = max_leverage - current_leverage
|
||||
if available_leverage > 0:
|
||||
available_value = balance * available_leverage
|
||||
lines.append(f"- 可开仓空间: ${available_value:,.2f} ({available_leverage:.1f}x)")
|
||||
else:
|
||||
lines.append("- ⚠️ 已达最大杠杆,不建议加仓")
|
||||
|
||||
# 当前交易对持仓
|
||||
positions = position_info.get('positions', [])
|
||||
symbol_positions = [p for p in positions if p.get('symbol') == symbol]
|
||||
|
||||
if symbol_positions:
|
||||
lines.append(f"\n**{symbol} 当前持仓**:")
|
||||
for pos in symbol_positions:
|
||||
side = "做多" if pos.get('side') == 'long' else "做空"
|
||||
entry = pos.get('entry_price', 0)
|
||||
pnl = pos.get('pnl_percent', 0)
|
||||
lines.append(f" - {side} @ ${entry:,.2f} | 盈亏: {pnl:+.2f}%")
|
||||
else:
|
||||
lines.append(f"\n**{symbol}**: 无持仓")
|
||||
|
||||
# 其他交易对持仓概况
|
||||
other_positions = [p for p in positions if p.get('symbol') != symbol and p.get('status') == 'open']
|
||||
if other_positions:
|
||||
lines.append(f"\n**其他持仓**: {len(other_positions)} 个")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def _build_data_prompt(self, symbol: str, data: Dict[str, pd.DataFrame],
|
||||
news_text: str = "") -> str:
|
||||
news_text: str = "", position_info: Dict[str, Any] = None) -> str:
|
||||
"""构建数据提示词"""
|
||||
parts = [f"# {symbol} 市场数据分析\n"]
|
||||
parts.append(f"分析时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||||
@ -219,6 +292,11 @@ class LLMSignalAnalyzer:
|
||||
current_price = float(data['5m'].iloc[-1]['close'])
|
||||
parts.append(f"**当前价格**: ${current_price:,.2f}\n")
|
||||
|
||||
# === 新增:账户和持仓信息 ===
|
||||
if position_info:
|
||||
parts.append("\n## 账户与持仓状态")
|
||||
parts.append(self._format_position_info(symbol, position_info))
|
||||
|
||||
# === 新增:关键价位分析 ===
|
||||
key_levels = self._calculate_key_levels(data)
|
||||
if key_levels:
|
||||
@ -871,6 +949,18 @@ class LLMSignalAnalyzer:
|
||||
if entry_type not in ['market', 'limit']:
|
||||
signal['entry_type'] = 'market' # 默认现价入场
|
||||
|
||||
# 验证仓位大小(默认根据等级设置)
|
||||
position_size = signal.get('position_size', '')
|
||||
if position_size not in ['heavy', 'medium', 'light']:
|
||||
# 根据信号等级设置默认仓位
|
||||
grade = signal.get('grade', 'C')
|
||||
if grade == 'A':
|
||||
signal['position_size'] = 'medium' # A级默认中仓
|
||||
elif grade == 'B':
|
||||
signal['position_size'] = 'light' # B级默认轻仓
|
||||
else:
|
||||
signal['position_size'] = 'light' # C级默认轻仓
|
||||
|
||||
return True
|
||||
|
||||
def _extract_summary(self, text: str) -> str:
|
||||
@ -946,6 +1036,12 @@ class LLMSignalAnalyzer:
|
||||
entry_type_text = '现价入场' if entry_type == 'market' else '挂单等待'
|
||||
entry_type_icon = '⚡' if entry_type == 'market' else '⏳'
|
||||
|
||||
# 仓位大小
|
||||
position_size = signal.get('position_size', 'light')
|
||||
position_map = {'heavy': '重仓', 'medium': '中仓', 'light': '轻仓'}
|
||||
position_icon = {'heavy': '🔥', 'medium': '📊', 'light': '🌱'}.get(position_size, '🌱')
|
||||
position_text = position_map.get(position_size, '轻仓')
|
||||
|
||||
# 计算风险收益比
|
||||
entry = signal.get('entry_price', 0)
|
||||
sl = signal.get('stop_loss', 0)
|
||||
@ -957,6 +1053,7 @@ class LLMSignalAnalyzer:
|
||||
|
||||
{action_icon} **方向**: {action}
|
||||
{entry_type_icon} **入场**: {entry_type_text}
|
||||
{position_icon} **仓位**: {position_text}
|
||||
⭐ **等级**: {grade} {grade_icon}
|
||||
📈 **置信度**: {confidence}%
|
||||
|
||||
@ -1006,6 +1103,12 @@ class LLMSignalAnalyzer:
|
||||
entry_type_text = '现价入场' if entry_type == 'market' else '挂单等待'
|
||||
entry_type_icon = '⚡' if entry_type == 'market' else '⏳'
|
||||
|
||||
# 仓位大小
|
||||
position_size = signal.get('position_size', 'light')
|
||||
position_map = {'heavy': '重仓', 'medium': '中仓', 'light': '轻仓'}
|
||||
position_icon = {'heavy': '🔥', 'medium': '📊', 'light': '🌱'}.get(position_size, '🌱')
|
||||
position_text = position_map.get(position_size, '轻仓')
|
||||
|
||||
# 标题和颜色
|
||||
if signal['action'] == 'buy':
|
||||
title = f"🟢 {symbol} {signal_type}做多信号 [{entry_type_text}]"
|
||||
@ -1025,6 +1128,7 @@ class LLMSignalAnalyzer:
|
||||
content_parts = [
|
||||
f"**{signal_type}** | **{grade}**{grade_icon} | **{confidence}%** 置信度",
|
||||
f"{entry_type_icon} **入场方式**: {entry_type_text}",
|
||||
f"{position_icon} **建议仓位**: {position_text}",
|
||||
"",
|
||||
f"💰 **入场**: ${entry:,.2f}",
|
||||
f"🛑 **止损**: ${sl:,.2f} ({sl_percent:+.1f}%)",
|
||||
|
||||
@ -131,9 +131,14 @@ class PaperTradingService:
|
||||
logger.info(f"D级信号不开仓: {signal.get('symbol')}")
|
||||
return result
|
||||
|
||||
# 固定使用保证金(不再根据等级区分)
|
||||
margin = self.margin_per_order # 每单固定 1000 USDT 保证金
|
||||
position_value = margin * self.leverage # 持仓价值 = 保证金 × 杠杆
|
||||
# === 动态仓位计算 ===
|
||||
position_size = signal.get('position_size', 'light')
|
||||
margin, position_value = self._calculate_dynamic_position(position_size, symbol)
|
||||
|
||||
if margin <= 0:
|
||||
logger.info(f"无可用保证金: {symbol} | 当前杠杆已达上限")
|
||||
return result
|
||||
|
||||
quantity = position_value # 订单数量(以 USDT 计价)
|
||||
|
||||
# 确定入场类型
|
||||
@ -198,6 +203,98 @@ class PaperTradingService:
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def _calculate_dynamic_position(self, position_size: str, symbol: str) -> tuple:
|
||||
"""
|
||||
根据 LLM 建议的仓位大小计算实际保证金和持仓价值
|
||||
|
||||
Args:
|
||||
position_size: 'heavy' / 'medium' / 'light'
|
||||
symbol: 交易对
|
||||
|
||||
Returns:
|
||||
(margin, position_value) 元组
|
||||
"""
|
||||
# 获取当前账户状态
|
||||
account = self.get_account_status()
|
||||
balance = account['current_balance']
|
||||
used_margin = account['used_margin']
|
||||
max_leverage = self.leverage # 最大杠杆 20x
|
||||
|
||||
# 计算可用保证金空间
|
||||
# 全仓模式下:最大持仓价值 = 余额 × 最大杠杆
|
||||
max_position_value = balance * max_leverage
|
||||
current_position_value = account['total_position_value']
|
||||
available_position_value = max_position_value - current_position_value
|
||||
|
||||
if available_position_value <= 0:
|
||||
logger.warning(f"已达最大杠杆限制,无法开仓")
|
||||
return 0, 0
|
||||
|
||||
# 根据 position_size 确定仓位比例
|
||||
# heavy: 可用空间的 30%
|
||||
# medium: 可用空间的 15%
|
||||
# light: 可用空间的 5%
|
||||
size_ratio = {
|
||||
'heavy': 0.30,
|
||||
'medium': 0.15,
|
||||
'light': 0.05
|
||||
}.get(position_size, 0.05)
|
||||
|
||||
# 计算目标持仓价值
|
||||
target_position_value = available_position_value * size_ratio
|
||||
|
||||
# 设置最小和最大限制
|
||||
min_position_value = 1000 # 最小持仓价值 1000 USDT
|
||||
max_single_position = balance * 5 # 单笔最大不超过 5x 杠杆
|
||||
|
||||
position_value = max(min_position_value, min(target_position_value, max_single_position))
|
||||
|
||||
# 确保不超过可用空间
|
||||
position_value = min(position_value, available_position_value)
|
||||
|
||||
# 计算对应的保证金
|
||||
margin = position_value / max_leverage
|
||||
|
||||
logger.info(f"动态仓位计算: {position_size} | 可用空间: ${available_position_value:,.0f} | "
|
||||
f"目标仓位: ${position_value:,.0f} | 保证金: ${margin:,.0f}")
|
||||
|
||||
return margin, position_value
|
||||
|
||||
def get_position_info(self) -> Dict[str, Any]:
|
||||
"""
|
||||
获取当前持仓信息(供 LLM 分析使用)
|
||||
|
||||
Returns:
|
||||
持仓信息字典
|
||||
"""
|
||||
account = self.get_account_status()
|
||||
active_orders = self.get_active_orders()
|
||||
|
||||
# 计算当前杠杆
|
||||
balance = account['current_balance']
|
||||
total_position_value = account['total_position_value']
|
||||
current_leverage = total_position_value / balance if balance > 0 else 0
|
||||
|
||||
# 格式化持仓列表
|
||||
positions = []
|
||||
for order in active_orders:
|
||||
positions.append({
|
||||
'symbol': order.get('symbol'),
|
||||
'side': order.get('side'),
|
||||
'status': order.get('status'),
|
||||
'entry_price': order.get('filled_price') or order.get('entry_price'),
|
||||
'quantity': order.get('quantity'),
|
||||
'pnl_percent': order.get('pnl_percent', 0)
|
||||
})
|
||||
|
||||
return {
|
||||
'account_balance': balance,
|
||||
'total_position_value': total_position_value,
|
||||
'current_leverage': current_leverage,
|
||||
'max_leverage': self.leverage,
|
||||
'positions': positions
|
||||
}
|
||||
|
||||
def check_price_triggers(self, symbol: str, current_price: float) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
检查当前价格是否触发挂单入场或止盈止损
|
||||
|
||||
Loading…
Reference in New Issue
Block a user