update
This commit is contained in:
parent
804da016da
commit
7e7ce7ecf4
@ -295,6 +295,10 @@ class CryptoAgent:
|
|||||||
logger.info(f" 支撑位: {support_str or '-'}")
|
logger.info(f" 支撑位: {support_str or '-'}")
|
||||||
logger.info(f" 阻力位: {resistance_str or '-'}")
|
logger.info(f" 阻力位: {resistance_str or '-'}")
|
||||||
|
|
||||||
|
# 2.5. 回顾并调整现有持仓(LLM 主动管理)
|
||||||
|
if self.paper_trading_enabled and self.paper_trading:
|
||||||
|
await self._review_and_adjust_positions(symbol, data)
|
||||||
|
|
||||||
# 3. 处理信号
|
# 3. 处理信号
|
||||||
signals = result.get('signals', [])
|
signals = result.get('signals', [])
|
||||||
|
|
||||||
@ -449,6 +453,156 @@ class CryptoAgent:
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
async def _review_and_adjust_positions(
|
||||||
|
self,
|
||||||
|
symbol: str,
|
||||||
|
data: Dict[str, pd.DataFrame]
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
回顾并调整现有持仓(LLM 主动管理)
|
||||||
|
|
||||||
|
每次分析后自动回顾该交易对的所有持仓,让 LLM 决定是否需要:
|
||||||
|
- 调整止损止盈
|
||||||
|
- 部分平仓
|
||||||
|
- 全部平仓
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 获取该交易对的所有活跃持仓(只看已成交的)
|
||||||
|
active_orders = self.paper_trading.get_active_orders()
|
||||||
|
positions = [
|
||||||
|
order for order in active_orders
|
||||||
|
if order.get('symbol') == symbol
|
||||||
|
and order.get('status') == 'open'
|
||||||
|
and order.get('filled_price') # 只处理已成交的订单
|
||||||
|
]
|
||||||
|
|
||||||
|
if not positions:
|
||||||
|
return # 没有持仓需要回顾
|
||||||
|
|
||||||
|
logger.info(f"\n🔄 【LLM 回顾持仓中...】共 {len(positions)} 个持仓")
|
||||||
|
|
||||||
|
# 准备持仓数据
|
||||||
|
position_data = []
|
||||||
|
for order in positions:
|
||||||
|
entry_price = order.get('filled_price') or order.get('entry_price', 0)
|
||||||
|
current_price = self.binance.get_ticker(symbol).get('lastPrice', entry_price)
|
||||||
|
if isinstance(current_price, str):
|
||||||
|
current_price = float(current_price)
|
||||||
|
|
||||||
|
# 计算盈亏百分比
|
||||||
|
side = order.get('side')
|
||||||
|
if side == 'long':
|
||||||
|
pnl_percent = ((current_price - entry_price) / entry_price) * 100
|
||||||
|
else:
|
||||||
|
pnl_percent = ((entry_price - current_price) / entry_price) * 100
|
||||||
|
|
||||||
|
position_data.append({
|
||||||
|
'order_id': order.get('order_id'),
|
||||||
|
'side': side,
|
||||||
|
'entry_price': entry_price,
|
||||||
|
'current_price': current_price,
|
||||||
|
'stop_loss': order.get('stop_loss', 0),
|
||||||
|
'take_profit': order.get('take_profit', 0),
|
||||||
|
'quantity': order.get('quantity', 0),
|
||||||
|
'pnl_percent': pnl_percent,
|
||||||
|
'open_time': order.get('open_time')
|
||||||
|
})
|
||||||
|
|
||||||
|
# 调用 LLM 回顾分析
|
||||||
|
decisions = await self.llm_analyzer.review_positions(symbol, position_data, data)
|
||||||
|
|
||||||
|
if not decisions:
|
||||||
|
logger.info(" LLM 建议保持所有持仓不变")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 执行 LLM 的调整建议
|
||||||
|
logger.info(f"\n📝 【LLM 调整建议】共 {len(decisions)} 个")
|
||||||
|
|
||||||
|
for decision in decisions:
|
||||||
|
order_id = decision.get('order_id')
|
||||||
|
action = decision.get('action')
|
||||||
|
reason = decision.get('reason', '')
|
||||||
|
|
||||||
|
action_map = {
|
||||||
|
'HOLD': '保持',
|
||||||
|
'ADJUST_SL_TP': '调整止损止盈',
|
||||||
|
'PARTIAL_CLOSE': '部分平仓',
|
||||||
|
'FULL_CLOSE': '全部平仓'
|
||||||
|
}
|
||||||
|
action_text = action_map.get(action, action)
|
||||||
|
|
||||||
|
logger.info(f" 订单 {order_id[:8]}: {action_text} - {reason}")
|
||||||
|
|
||||||
|
# 执行调整
|
||||||
|
result = self.paper_trading.adjust_position_by_llm(
|
||||||
|
order_id=order_id,
|
||||||
|
action=action,
|
||||||
|
new_sl=decision.get('new_sl'),
|
||||||
|
new_tp=decision.get('new_tp'),
|
||||||
|
close_percent=decision.get('close_percent')
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.get('success'):
|
||||||
|
# 发送通知
|
||||||
|
await self._notify_position_adjustment(symbol, order_id, decision, result)
|
||||||
|
|
||||||
|
# 如果是平仓操作,从活跃订单中移除
|
||||||
|
if action in ['PARTIAL_CLOSE', 'FULL_CLOSE']:
|
||||||
|
closed_result = result.get('pnl', {})
|
||||||
|
if closed_result:
|
||||||
|
pnl = closed_result.get('pnl', 0)
|
||||||
|
pnl_percent = closed_result.get('pnl_percent', 0)
|
||||||
|
logger.info(f" ✅ 已平仓: PnL ${pnl:+.2f} ({pnl_percent:+.1f}%)")
|
||||||
|
else:
|
||||||
|
logger.warning(f" ❌ 执行失败: {result.get('error')}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"持仓回顾失败: {e}", exc_info=True)
|
||||||
|
|
||||||
|
async def _notify_position_adjustment(
|
||||||
|
self,
|
||||||
|
symbol: str,
|
||||||
|
order_id: str,
|
||||||
|
decision: Dict[str, Any],
|
||||||
|
result: Dict[str, Any]
|
||||||
|
):
|
||||||
|
"""发送持仓调整通知"""
|
||||||
|
action = decision.get('action')
|
||||||
|
reason = decision.get('reason', '')
|
||||||
|
|
||||||
|
action_map = {
|
||||||
|
'ADJUST_SL_TP': '🔄 调整止损止盈',
|
||||||
|
'PARTIAL_CLOSE': '📤 部分平仓',
|
||||||
|
'FULL_CLOSE': '🚪 全部平仓'
|
||||||
|
}
|
||||||
|
action_text = action_map.get(action, action)
|
||||||
|
|
||||||
|
message = f"""{action_text}
|
||||||
|
|
||||||
|
交易对: {symbol}
|
||||||
|
订单: {order_id[:8]}
|
||||||
|
原因: {reason}"""
|
||||||
|
|
||||||
|
if action == 'ADJUST_SL_TP':
|
||||||
|
changes = result.get('changes', [])
|
||||||
|
message += f"\n调整内容: {', '.join(changes)}"
|
||||||
|
|
||||||
|
elif action in ['PARTIAL_CLOSE', 'FULL_CLOSE']:
|
||||||
|
pnl_info = result.get('pnl', {})
|
||||||
|
if pnl_info:
|
||||||
|
pnl = pnl_info.get('pnl', 0)
|
||||||
|
pnl_percent = pnl_info.get('pnl_percent', 0)
|
||||||
|
message += f"\n实现盈亏: ${pnl:+.2f} ({pnl_percent:+.1f}%)"
|
||||||
|
|
||||||
|
if action == 'PARTIAL_CLOSE':
|
||||||
|
close_percent = decision.get('close_percent', 0)
|
||||||
|
remaining = result.get('remaining_quantity', 0)
|
||||||
|
message += f"\n平仓比例: {close_percent:.0f}%"
|
||||||
|
message += f"\n剩余仓位: ${remaining:,.0f}"
|
||||||
|
|
||||||
|
await self.feishu.send_text(message)
|
||||||
|
await self.telegram.send_message(message)
|
||||||
|
|
||||||
async def analyze_once(self, symbol: str) -> Dict[str, Any]:
|
async def analyze_once(self, symbol: str) -> Dict[str, Any]:
|
||||||
"""单次分析(用于测试或手动触发)"""
|
"""单次分析(用于测试或手动触发)"""
|
||||||
data = self.binance.get_multi_timeframe_data(symbol)
|
data = self.binance.get_multi_timeframe_data(symbol)
|
||||||
|
|||||||
@ -1180,3 +1180,242 @@ class LLMSignalAnalyzer:
|
|||||||
'content': '\n'.join(content_parts),
|
'content': '\n'.join(content_parts),
|
||||||
'color': color
|
'color': color
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 持仓回顾分析的 System Prompt
|
||||||
|
POSITION_REVIEW_PROMPT = """你是一个专业的加密货币交易风险管理专家。你的任务是回顾现有持仓,根据最新市场行情决定是否需要调整。
|
||||||
|
|
||||||
|
## 你的职责
|
||||||
|
|
||||||
|
对于每个持仓,你需要分析:
|
||||||
|
1. 当前持仓状态(盈亏、持仓时间、风险敞口)
|
||||||
|
2. 最新市场行情(趋势、支撑阻力、技术指标)
|
||||||
|
3. 原有交易逻辑是否依然有效
|
||||||
|
4. 是否需要调整止损止盈
|
||||||
|
5. 是否需要平仓(部分或全部)
|
||||||
|
|
||||||
|
## 决策类型
|
||||||
|
|
||||||
|
### 1. HOLD(保持)
|
||||||
|
- 适用场景:行情符合预期,趋势延续
|
||||||
|
- 操作:不改变任何设置
|
||||||
|
|
||||||
|
### 2. ADJUST_SL_TP(调整止损止盈)
|
||||||
|
- 适用场景:
|
||||||
|
- **盈利状态**:趋势强劲,可以收紧止损锁定更多利润
|
||||||
|
- **亏损状态**:支撑/阻力位变化,需要调整止损到更合理位置
|
||||||
|
- **目标接近**:原止盈目标接近,但趋势仍强,可上移止盈
|
||||||
|
- 操作:更新 stop_loss 和/或 take_profit
|
||||||
|
|
||||||
|
### 3. PARTIAL_CLOSE(部分平仓)
|
||||||
|
- 适用场景:
|
||||||
|
- 盈利较大,但不确定性增加
|
||||||
|
- 重要阻力位附近,锁定部分利润
|
||||||
|
- 趋势有转弱迹象
|
||||||
|
- 操作:平掉 close_percent 比例的仓位
|
||||||
|
|
||||||
|
### 4. FULL_CLOSE(全部平仓)
|
||||||
|
- 适用场景:
|
||||||
|
- **止损型**:趋势明确反转,止损信号出现
|
||||||
|
- **止盈型**:目标达成,或出现更好的机会
|
||||||
|
- **风险型**:重大利空/利好的不确定性
|
||||||
|
- 操作:平掉全部仓位
|
||||||
|
|
||||||
|
## 调整原则
|
||||||
|
|
||||||
|
### 盈利状态(盈亏 > 0)
|
||||||
|
1. **收紧止损**:如果盈利 > 2%,可以将止损移至保本或盈利 1% 位置
|
||||||
|
2. **部分止盈**:如果盈利 > 5% 且接近重要阻力位,可平掉 30-50% 仓位
|
||||||
|
3. **继续持有**:如果趋势强劲,可以放宽止损让利润奔跑
|
||||||
|
|
||||||
|
### 亏损状态(盈亏 < 0)
|
||||||
|
1. **提前止损**:如果出现明确的反转信号,不要等止损触发
|
||||||
|
2. **调整止损**:如果关键支撑/阻力位变化,更新止损位置
|
||||||
|
3. **继续持有**:如果只是正常波动,原交易逻辑未变,继续持有
|
||||||
|
|
||||||
|
### 重要技术信号
|
||||||
|
1. **趋势反转**:多周期共振转反、跌破/突破关键 MA
|
||||||
|
2. **量价背离**:价格新高但成交量萎缩
|
||||||
|
3. **MACD 背离**:价格新高/新低但 MACD 未确认
|
||||||
|
4. **RSI 极端**:RSI > 75 或 < 25 后掉头
|
||||||
|
|
||||||
|
## 输出格式
|
||||||
|
|
||||||
|
对于每个持仓,输出 JSON:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"order_id": "订单ID",
|
||||||
|
"action": "HOLD | ADJUST_SL_TP | PARTIAL_CLOSE | FULL_CLOSE",
|
||||||
|
"new_sl": 新止损价格(仅 ADJUST_SL_TP 时),
|
||||||
|
"new_tp": 新止盈价格(仅 ADJUST_SL_TP 时),
|
||||||
|
"close_percent": 平仓比例 0-100(仅 PARTIAL_CLOSE 时),
|
||||||
|
"reason": "调整原因(简明扼要,20字以内)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 重要原则
|
||||||
|
1. **主动管理**:不要被动等待止损触发,主动识别风险
|
||||||
|
2. **保护利润**:盈利状态下,优先考虑锁定利润
|
||||||
|
3. **果断止损**:亏损状态下,如果趋势反转,果断离场
|
||||||
|
4. **灵活调整**:根据最新行情,不局限于开仓时的判断
|
||||||
|
5. **考虑成本**:频繁调整会增加交易成本,只在有明确信号时调整
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def review_positions(
|
||||||
|
self,
|
||||||
|
symbol: str,
|
||||||
|
positions: List[Dict[str, Any]],
|
||||||
|
data: Dict[str, pd.DataFrame]
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
回顾并分析现有持仓,给出调整建议
|
||||||
|
|
||||||
|
Args:
|
||||||
|
symbol: 交易对
|
||||||
|
positions: 持仓列表,每个持仓包含:
|
||||||
|
- order_id: 订单ID
|
||||||
|
- side: 'long' or 'short'
|
||||||
|
- entry_price: 开仓价格
|
||||||
|
- current_price: 当前价格
|
||||||
|
- stop_loss: 当前止损
|
||||||
|
- take_profit: 当前止盈
|
||||||
|
- quantity: 仓位数量
|
||||||
|
- pnl_percent: 盈亏百分比
|
||||||
|
- open_time: 开仓时间
|
||||||
|
data: 多周期K线数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
调整建议列表
|
||||||
|
"""
|
||||||
|
if not positions:
|
||||||
|
return []
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 构建持仓分析提示
|
||||||
|
prompt = self._build_position_review_prompt(symbol, positions, data)
|
||||||
|
|
||||||
|
# 调用 LLM
|
||||||
|
response = llm_service.chat([
|
||||||
|
{"role": "system", "content": self.POSITION_REVIEW_PROMPT},
|
||||||
|
{"role": "user", "content": prompt}
|
||||||
|
], model_override=self.model_override)
|
||||||
|
|
||||||
|
if not response:
|
||||||
|
logger.warning(f"{symbol} 持仓回顾 LLM 分析无响应")
|
||||||
|
return []
|
||||||
|
|
||||||
|
# 解析响应
|
||||||
|
return self._parse_position_review_response(response)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"持仓回顾分析失败: {e}", exc_info=True)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _build_position_review_prompt(
|
||||||
|
self,
|
||||||
|
symbol: str,
|
||||||
|
positions: List[Dict[str, Any]],
|
||||||
|
data: Dict[str, pd.DataFrame]
|
||||||
|
) -> str:
|
||||||
|
"""构建持仓分析提示"""
|
||||||
|
lines = [f"# {symbol} 持仓回顾分析", f"分析时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"]
|
||||||
|
lines.append("\n## 当前持仓")
|
||||||
|
|
||||||
|
for idx, pos in enumerate(positions, 1):
|
||||||
|
side_text = "做多 📈" if pos['side'] == 'long' else "做空 📉"
|
||||||
|
pnl_text = f"+{pos['pnl_percent']:.1f}%" if pos['pnl_percent'] >= 0 else f"{pos['pnl_percent']:.1f}%"
|
||||||
|
pnl_emoji = "✅" if pos['pnl_percent'] >= 0 else "❌"
|
||||||
|
|
||||||
|
lines.append(f"\n### 持仓 {idx}: {pos['order_id']}")
|
||||||
|
lines.append(f"- 方向: {side_text}")
|
||||||
|
lines.append(f"- 开仓价: ${pos['entry_price']:,.2f}")
|
||||||
|
lines.append(f"- 当前价: ${pos['current_price']:,.2f}")
|
||||||
|
lines.append(f"- 盈亏: {pnl_emoji} {pnl_text}")
|
||||||
|
lines.append(f"- 止损: ${pos['stop_loss']:,.2f}")
|
||||||
|
lines.append(f"- 止盈: ${pos['take_profit']:,.2f}")
|
||||||
|
lines.append(f"- 仓位: ${pos['quantity']:,.0f}")
|
||||||
|
|
||||||
|
# 计算持仓时间
|
||||||
|
if 'open_time' in pos:
|
||||||
|
open_time = pos['open_time']
|
||||||
|
if isinstance(open_time, str):
|
||||||
|
open_time = datetime.fromisoformat(open_time)
|
||||||
|
duration = datetime.now() - open_time
|
||||||
|
hours = duration.total_seconds() / 3600
|
||||||
|
lines.append(f"- 持仓时间: {hours:.1f} 小时")
|
||||||
|
|
||||||
|
# 添加市场分析
|
||||||
|
lines.append("\n## 最新市场分析")
|
||||||
|
|
||||||
|
# 使用 1h 和 4h 数据分析
|
||||||
|
for interval in ['4h', '1h']:
|
||||||
|
df = data.get(interval)
|
||||||
|
if df is None or len(df) < 20:
|
||||||
|
continue
|
||||||
|
|
||||||
|
latest = df.iloc[-1]
|
||||||
|
prev = df.iloc[-2]
|
||||||
|
|
||||||
|
lines.append(f"\n### {interval} 周期")
|
||||||
|
lines.append(f"- 当前价格: ${latest['close']:,.2f}")
|
||||||
|
lines.append(f"- 涨跌幅: {((latest['close'] - prev['close']) / prev['close'] * 100):+.2f}%")
|
||||||
|
|
||||||
|
if 'ma5' in df.columns and pd.notna(latest['ma5']):
|
||||||
|
lines.append(f"- MA5: ${latest['ma5']:,.2f}")
|
||||||
|
if 'ma20' in df.columns and pd.notna(latest['ma20']):
|
||||||
|
lines.append(f"- MA20: ${latest['ma20']:,.2f}")
|
||||||
|
|
||||||
|
if 'rsi' in df.columns and pd.notna(latest['rsi']):
|
||||||
|
rsi_val = latest['rsi']
|
||||||
|
rsi_status = "超买" if rsi_val > 70 else "超卖" if rsi_val < 30 else "正常"
|
||||||
|
lines.append(f"- RSI: {rsi_val:.1f} ({rsi_status})")
|
||||||
|
|
||||||
|
if 'macd' in df.columns and pd.notna(latest['macd']):
|
||||||
|
macd_trend = "多头" if latest['macd'] > 0 else "空头"
|
||||||
|
lines.append(f"- MACD: {latest['macd']:.4f} ({macd_trend})")
|
||||||
|
|
||||||
|
# 添加趋势判断
|
||||||
|
lines.append("\n## 请给出调整建议")
|
||||||
|
lines.append("对于每个持仓,请分析是否需要调整,并按 JSON 格式输出。")
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def _parse_position_review_response(self, response: str) -> List[Dict[str, Any]]:
|
||||||
|
"""解析持仓回顾响应"""
|
||||||
|
try:
|
||||||
|
# 尝试提取 JSON 数组
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
# 查找 JSON 数组
|
||||||
|
json_match = re.search(r'\[\s*\{.*?\}\s*\]', response, re.DOTALL)
|
||||||
|
if json_match:
|
||||||
|
json_str = json_match.group(0)
|
||||||
|
decisions = json.loads(json_str)
|
||||||
|
|
||||||
|
# 验证每个决策的格式
|
||||||
|
valid_decisions = []
|
||||||
|
for decision in decisions:
|
||||||
|
if 'order_id' in decision and 'action' in decision:
|
||||||
|
valid_decisions.append(decision)
|
||||||
|
else:
|
||||||
|
logger.warning(f"无效的决策格式: {decision}")
|
||||||
|
|
||||||
|
return valid_decisions
|
||||||
|
|
||||||
|
# 如果找不到 JSON 数组,尝试解析单个对象
|
||||||
|
json_match = re.search(r'\{[^{}]*"action"[^{}]*\}', response, re.DOTALL)
|
||||||
|
if json_match:
|
||||||
|
json_str = json_match.group(0)
|
||||||
|
decision = json.loads(json_str)
|
||||||
|
if 'order_id' in decision and 'action' in decision:
|
||||||
|
return [decision]
|
||||||
|
|
||||||
|
logger.warning(f"无法解析持仓回顾响应: {response[:200]}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
logger.error(f"解析持仓回顾 JSON 失败: {e}")
|
||||||
|
return []
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"解析持仓回顾响应时出错: {e}")
|
||||||
|
return []
|
||||||
|
|||||||
@ -1304,6 +1304,178 @@ class PaperTradingService:
|
|||||||
|
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def adjust_position_by_llm(
|
||||||
|
self,
|
||||||
|
order_id: str,
|
||||||
|
action: str,
|
||||||
|
new_sl: Optional[float] = None,
|
||||||
|
new_tp: Optional[float] = None,
|
||||||
|
close_percent: Optional[float] = None
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
根据 LLM 建议调整持仓
|
||||||
|
|
||||||
|
Args:
|
||||||
|
order_id: 订单ID
|
||||||
|
action: 操作类型 ('HOLD', 'ADJUST_SL_TP', 'PARTIAL_CLOSE', 'FULL_CLOSE')
|
||||||
|
new_sl: 新止损价格
|
||||||
|
new_tp: 新止盈价格
|
||||||
|
close_percent: 平仓比例 (0-100)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
调整结果
|
||||||
|
"""
|
||||||
|
if order_id not in self.active_orders:
|
||||||
|
return {'success': False, 'error': f'订单 {order_id} 不存在或已关闭'}
|
||||||
|
|
||||||
|
order = self.active_orders[order_id]
|
||||||
|
|
||||||
|
# 如果订单是挂单状态,不能调整
|
||||||
|
if order.status != OrderStatus.OPEN:
|
||||||
|
return {'success': False, 'error': f'订单状态为 {order.status.value},无法调整'}
|
||||||
|
|
||||||
|
db = db_service.get_session()
|
||||||
|
try:
|
||||||
|
if action == 'HOLD':
|
||||||
|
return {'success': True, 'message': '保持持仓不变'}
|
||||||
|
|
||||||
|
elif action == 'ADJUST_SL_TP':
|
||||||
|
# 调整止损止盈
|
||||||
|
updated = False
|
||||||
|
changes = []
|
||||||
|
|
||||||
|
if new_sl is not None:
|
||||||
|
order.stop_loss = new_sl
|
||||||
|
changes.append(f"止损→${new_sl:,.2f}")
|
||||||
|
updated = True
|
||||||
|
|
||||||
|
if new_tp is not None:
|
||||||
|
order.take_profit = new_tp
|
||||||
|
changes.append(f"止盈→${new_tp:,.2f}")
|
||||||
|
updated = True
|
||||||
|
|
||||||
|
if updated:
|
||||||
|
db.commit()
|
||||||
|
logger.info(f"LLM 调整订单 {order_id}: {', '.join(changes)}")
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'action': 'adjusted',
|
||||||
|
'changes': changes,
|
||||||
|
'order': order.to_dict()
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {'success': False, 'error': '未提供新的止损或止盈价格'}
|
||||||
|
|
||||||
|
elif action == 'PARTIAL_CLOSE':
|
||||||
|
# 部分平仓
|
||||||
|
if close_percent is None or close_percent <= 0 or close_percent > 100:
|
||||||
|
return {'success': False, 'error': f'无效的平仓比例: {close_percent}'}
|
||||||
|
|
||||||
|
# 计算平仓数量
|
||||||
|
close_quantity = order.quantity * (close_percent / 100)
|
||||||
|
remaining_quantity = order.quantity - close_quantity
|
||||||
|
|
||||||
|
if remaining_quantity < 10: # 剩余数量太小,直接全部平仓
|
||||||
|
return self._close_order_llm(order, db, 'PARTIAL_CLOSE', '部分平仓后剩余过少,直接全部平仓')
|
||||||
|
|
||||||
|
# 执行部分平仓
|
||||||
|
entry_price = order.filled_price or order.entry_price
|
||||||
|
current_price = self._get_current_price(order.symbol)
|
||||||
|
pnl = self._calculate_pnl(order, current_price)
|
||||||
|
|
||||||
|
# 更新订单数量
|
||||||
|
order.quantity = remaining_quantity
|
||||||
|
|
||||||
|
# 记录部分平仓
|
||||||
|
logger.info(f"LLM 部分平仓: {order_id} 平掉 {close_percent:.1f}% ({close_quantity:.0f})")
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'action': 'partial_close',
|
||||||
|
'close_percent': close_percent,
|
||||||
|
'close_quantity': close_quantity,
|
||||||
|
'remaining_quantity': remaining_quantity,
|
||||||
|
'pnl': pnl,
|
||||||
|
'order': order.to_dict()
|
||||||
|
}
|
||||||
|
|
||||||
|
elif action == 'FULL_CLOSE':
|
||||||
|
# 全部平仓
|
||||||
|
return self._close_order_llm(order, db, 'FULL_CLOSE', 'LLM 建议平仓')
|
||||||
|
|
||||||
|
else:
|
||||||
|
return {'success': False, 'error': f'未知的操作类型: {action}'}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
|
logger.error(f"调整订单失败: {e}", exc_info=True)
|
||||||
|
return {'success': False, 'error': str(e)}
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
def _close_order_llm(
|
||||||
|
self,
|
||||||
|
order: PaperOrder,
|
||||||
|
db,
|
||||||
|
close_reason: str,
|
||||||
|
reason_detail: str
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
LLM 触发的平仓操作
|
||||||
|
|
||||||
|
Args:
|
||||||
|
order: 订单对象
|
||||||
|
db: 数据库会话
|
||||||
|
close_reason: 平仓原因类型
|
||||||
|
reason_detail: 详细原因
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
平仓结果
|
||||||
|
"""
|
||||||
|
# 获取当前价格
|
||||||
|
current_price = self._get_current_price(order.symbol)
|
||||||
|
|
||||||
|
# 计算盈亏
|
||||||
|
pnl = self._calculate_pnl(order, current_price)
|
||||||
|
|
||||||
|
# 更新订单状态
|
||||||
|
order.status = OrderStatus.CLOSED
|
||||||
|
order.close_price = current_price
|
||||||
|
order.close_time = datetime.utcnow()
|
||||||
|
order.pnl = pnl['pnl']
|
||||||
|
order.pnl_percent = pnl['pnl_percent']
|
||||||
|
order.close_reason = close_reason
|
||||||
|
|
||||||
|
# 从活跃订单中移除
|
||||||
|
if order.order_id in self.active_orders:
|
||||||
|
del self.active_orders[order.order_id]
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
logger.info(f"LLM 平仓: {order.order_id} | 原因: {reason_detail} | 盈亏: ${pnl['pnl']:+.2f} ({pnl['pnl_percent']:+.1f}%)")
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'action': 'closed',
|
||||||
|
'close_reason': close_reason,
|
||||||
|
'reason_detail': reason_detail,
|
||||||
|
'pnl': pnl,
|
||||||
|
'order': order.to_dict()
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_current_price(self, symbol: str) -> float:
|
||||||
|
"""获取交易对当前价格"""
|
||||||
|
try:
|
||||||
|
from app.services.binance_service import binance_service
|
||||||
|
ticker = binance_service.get_ticker(symbol)
|
||||||
|
if ticker and 'lastPrice' in ticker:
|
||||||
|
return float(ticker['lastPrice'])
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"获取 {symbol} 当前价格失败: {e}")
|
||||||
|
return 0.0
|
||||||
|
|
||||||
def reset_all_data(self) -> Dict[str, Any]:
|
def reset_all_data(self) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
重置所有模拟交易数据
|
重置所有模拟交易数据
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user