优化顺势而为策略
This commit is contained in:
parent
faa551e661
commit
f6e15d4a48
@ -514,9 +514,9 @@ class CryptoAgent:
|
||||
# 模拟交易决策
|
||||
if paper_trading_enabled:
|
||||
logger.info(f"\n📊 【模拟交易决策】")
|
||||
positions, account = self._get_trading_state(use_real_trading=False)
|
||||
positions, account, pending_orders = self._get_trading_state(use_real_trading=False)
|
||||
paper_decision = await self.decision_maker.make_decision(
|
||||
market_signal, positions, account, current_price
|
||||
market_signal, positions, account, current_price, pending_orders
|
||||
)
|
||||
self._log_trading_decision(paper_decision)
|
||||
else:
|
||||
@ -527,9 +527,9 @@ class CryptoAgent:
|
||||
logger.info(f"\n💰 【实盘交易决策】")
|
||||
# 检查是否开启自动交易
|
||||
if self.real_trading and self.real_trading.get_auto_trading_status():
|
||||
positions, account = self._get_trading_state(use_real_trading=True)
|
||||
positions, account, pending_orders = self._get_trading_state(use_real_trading=True)
|
||||
real_decision = await self.decision_maker.make_decision(
|
||||
market_signal, positions, account, current_price
|
||||
market_signal, positions, account, current_price, pending_orders
|
||||
)
|
||||
self._log_trading_decision(real_decision)
|
||||
else:
|
||||
@ -657,6 +657,9 @@ class CryptoAgent:
|
||||
|
||||
Args:
|
||||
use_real_trading: True 获取实盘状态,False 获取模拟交易状态
|
||||
|
||||
Returns:
|
||||
(positions, account, pending_orders) - 持仓列表、账户状态、挂单列表
|
||||
"""
|
||||
if use_real_trading and self.real_trading:
|
||||
# 实盘交易
|
||||
@ -667,21 +670,33 @@ class CryptoAgent:
|
||||
active_orders = self.paper_trading.get_active_orders()
|
||||
account = self.paper_trading.get_account_status()
|
||||
|
||||
# 转换为标准格式(只返回已成交的订单作为持仓)
|
||||
# 分离持仓和挂单
|
||||
position_list = []
|
||||
pending_orders = []
|
||||
for order in active_orders:
|
||||
# 只返回已成交(OPEN 状态且有 filled_price)的订单
|
||||
if order.get('status') == 'open' and order.get('filled_price'):
|
||||
# 已成交的订单作为持仓
|
||||
position_list.append({
|
||||
'symbol': order.get('symbol'),
|
||||
'side': order.get('side'),
|
||||
'holding': order.get('quantity', 0), # 使用 quantity 作为持仓量
|
||||
'holding': order.get('quantity', 0),
|
||||
'entry_price': order.get('filled_price') or order.get('entry_price'),
|
||||
'stop_loss': order.get('stop_loss'),
|
||||
'take_profit': order.get('take_profit')
|
||||
})
|
||||
elif order.get('status') == 'pending':
|
||||
# 未成交的订单作为挂单
|
||||
pending_orders.append({
|
||||
'order_id': order.get('order_id'),
|
||||
'symbol': order.get('symbol'),
|
||||
'side': order.get('side'),
|
||||
'entry_price': order.get('entry_price'),
|
||||
'quantity': order.get('quantity', 0),
|
||||
'entry_type': order.get('entry_type', 'market'),
|
||||
'confidence': order.get('confidence', 0)
|
||||
})
|
||||
|
||||
return position_list, account
|
||||
return position_list, account, pending_orders
|
||||
|
||||
async def _execute_decisions(self, paper_decision: Dict[str, Any],
|
||||
real_decision: Dict[str, Any],
|
||||
@ -721,22 +736,28 @@ class CryptoAgent:
|
||||
logger.info(f"\n📊 【执行模拟交易】")
|
||||
|
||||
if decision_type in ['OPEN', 'ADD']:
|
||||
# 先执行交易
|
||||
result = await self._execute_paper_trade(paper_decision, market_signal, current_price)
|
||||
# 检查是否成功执行
|
||||
order = result.get('order') if result else None
|
||||
if order:
|
||||
# 只有成功创建订单后才发送通知
|
||||
await self._send_signal_notification(market_signal, paper_decision, current_price, is_paper=True)
|
||||
paper_executed = True
|
||||
else:
|
||||
# 有信号但订单创建失败,发送未执行通知
|
||||
reason = result.get('message', '订单创建失败') if result else '订单创建失败'
|
||||
await self._notify_signal_not_executed(market_signal, paper_decision, current_price, is_paper=True, reason=reason)
|
||||
logger.warning(f" ⚠️ 模拟交易未执行,已发送通知")
|
||||
# 先执行交易
|
||||
result = await self._execute_paper_trade(paper_decision, market_signal, current_price)
|
||||
# 检查是否成功执行
|
||||
order = result.get('order') if result else None
|
||||
if order:
|
||||
# 只有成功创建订单后才发送通知
|
||||
await self._send_signal_notification(market_signal, paper_decision, current_price, is_paper=True)
|
||||
paper_executed = True
|
||||
else:
|
||||
# 有信号但订单创建失败,发送未执行通知
|
||||
reason = result.get('message', '订单创建失败') if result else '订单创建失败'
|
||||
await self._notify_signal_not_executed(market_signal, paper_decision, current_price, is_paper=True, reason=reason)
|
||||
logger.warning(f" ⚠️ 模拟交易未执行,已发送通知")
|
||||
elif decision_type == 'CLOSE':
|
||||
await self._execute_close(paper_decision, paper_trading=True)
|
||||
paper_executed = True
|
||||
elif decision_type == 'CANCEL_PENDING':
|
||||
await self._execute_cancel_pending(paper_decision, paper_trading=True)
|
||||
paper_executed = True
|
||||
elif decision_type == 'REDUCE':
|
||||
await self._execute_reduce(paper_decision, paper_trading=True)
|
||||
paper_executed = True
|
||||
|
||||
# ============================================================
|
||||
# 执行实盘交易决策
|
||||
@ -770,6 +791,12 @@ class CryptoAgent:
|
||||
elif decision_type == 'CLOSE':
|
||||
await self._execute_close(real_decision, paper_trading=False)
|
||||
real_executed = True
|
||||
elif decision_type == 'CANCEL_PENDING':
|
||||
await self._execute_cancel_pending(real_decision, paper_trading=False)
|
||||
real_executed = True
|
||||
elif decision_type == 'REDUCE':
|
||||
await self._execute_reduce(real_decision, paper_trading=False)
|
||||
real_executed = True
|
||||
|
||||
# 如果都没有执行,给出提示
|
||||
if not paper_executed and not real_executed:
|
||||
@ -1260,6 +1287,76 @@ class CryptoAgent:
|
||||
except Exception as e:
|
||||
logger.error(f"执行平仓失败: {e}")
|
||||
|
||||
async def _execute_cancel_pending(self, decision: Dict[str, Any], paper_trading: bool = True):
|
||||
"""执行取消挂单
|
||||
|
||||
Args:
|
||||
decision: 交易决策
|
||||
paper_trading: True=模拟交易, False=实盘交易
|
||||
"""
|
||||
try:
|
||||
symbol = decision.get('symbol')
|
||||
orders_to_cancel = decision.get('orders_to_cancel', [])
|
||||
|
||||
if not orders_to_cancel:
|
||||
logger.info(f" ⚠️ 没有需要取消的订单")
|
||||
return
|
||||
|
||||
trading_service = self.paper_trading if paper_trading else self.real_trading
|
||||
trading_type = "模拟" if paper_trading else "实盘"
|
||||
|
||||
if not trading_service:
|
||||
logger.warning(f" {trading_type}交易服务未初始化")
|
||||
return
|
||||
|
||||
logger.info(f" 🚫 {trading_type}取消挂单: {symbol}")
|
||||
logger.info(f" 取消订单数量: {len(orders_to_cancel)}")
|
||||
|
||||
cancelled_count = 0
|
||||
for order_id in orders_to_cancel:
|
||||
try:
|
||||
# 取消订单
|
||||
result = trading_service.cancel_order(order_id)
|
||||
if result.get('success'):
|
||||
cancelled_count += 1
|
||||
logger.info(f" ✅ 已取消订单: {order_id}")
|
||||
else:
|
||||
logger.warning(f" ⚠️ 取消订单失败: {order_id} | {result.get('message', '')}")
|
||||
except Exception as e:
|
||||
logger.error(f" ❌ 取消订单异常: {order_id} | {e}")
|
||||
|
||||
logger.info(f" 📊 成功取消 {cancelled_count}/{len(orders_to_cancel)} 个订单")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"执行取消挂单失败: {e}")
|
||||
|
||||
async def _execute_reduce(self, decision: Dict[str, Any], paper_trading: bool = True):
|
||||
"""执行减仓
|
||||
|
||||
Args:
|
||||
decision: 交易决策
|
||||
paper_trading: True=模拟交易, False=实盘交易
|
||||
"""
|
||||
try:
|
||||
symbol = decision.get('symbol')
|
||||
trading_type = "模拟" if paper_trading else "实盘"
|
||||
|
||||
logger.info(f" 📤 {trading_type}减仓: {symbol}")
|
||||
logger.info(f" 理由: {decision.get('reasoning', '')}")
|
||||
|
||||
trading_service = self.paper_trading if paper_trading else self.real_trading
|
||||
|
||||
if not trading_service:
|
||||
logger.warning(f" {trading_type}交易服务未初始化")
|
||||
return
|
||||
|
||||
# TODO: 实现减仓逻辑
|
||||
# 减仓可以是部分平仓,需要根据决策中的参数执行
|
||||
logger.info(f" ⚠️ 减仓功能待实现")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"执行减仓失败: {e}")
|
||||
|
||||
def _convert_to_paper_signal(self, symbol: str, signal: Dict[str, Any],
|
||||
current_price: float) -> Dict[str, Any]:
|
||||
"""转换 LLM 信号格式为模拟交易格式"""
|
||||
|
||||
@ -33,6 +33,22 @@ class TradingDecisionMaker:
|
||||
3. 账户状态(余额、已用保证金、杠杆等)
|
||||
|
||||
## 决策类型
|
||||
### 🚨 铁律(必须首先检查)
|
||||
**趋势与信号方向一致性检查(第一优先级)**:
|
||||
|
||||
| 当前趋势 | 信号方向 | 允许操作 | 条件 |
|
||||
|---------|---------|---------|------|
|
||||
| `uptrend` (上升) | buy (做多) | ✅ 允许 | 正常仓位 |
|
||||
| `uptrend` (上升) | sell (做空) | ❌ 禁止 | **除非**有极强的多重反转信号(背离+放量+关键形态)+ confidence >= 85 |
|
||||
| `downtrend` (下降) | sell (做空) | ✅ 允许 | 正常仓位 |
|
||||
| `downtrend` (下降) | buy (做多) | ❌ 禁止 | **除非**有极强的多重反转信号(背离+放量+关键形态)+ confidence >= 85 |
|
||||
| `neutral` (震荡) | buy/sell | ✅ 允许 | 轻仓操作 |
|
||||
|
||||
**趋势强度限制**:
|
||||
- `strong` 趋势:**严禁逆势开仓**,直接返回 HOLD
|
||||
- `medium` 趋势:逆势开仓需 confidence >= 85 + 多重反转信号
|
||||
- `weak` 或 `neutral`:可双向交易,但谨慎仓位
|
||||
|
||||
### 1. 开仓(OPEN)
|
||||
**时机**:无持仓或可以加仓时
|
||||
**要求**:
|
||||
@ -43,6 +59,34 @@ class TradingDecisionMaker:
|
||||
- 账户有足够的可用杠杆空间
|
||||
- 风险可控(止损明确)
|
||||
|
||||
**顺势 vs 逆势仓位规则(重要)**:
|
||||
|
||||
| 情况 | 仓位限制 | 保证金倍率 |
|
||||
|-----|---------|-----------|
|
||||
| **顺势交易**(信号与趋势同向) | 正常仓位 | 100% |
|
||||
| **震荡市交易**(neutral 趋势) | 降级处理 | 70% |
|
||||
| **逆势交易**(信号与趋势反向) | **大幅降级** | 30% |
|
||||
|
||||
**具体规则**:
|
||||
- 顺势 A 级 → heavy(12% 保证金)
|
||||
- 顺势 B 级 → medium(6% 保证金)
|
||||
- 顺势 C 级 → light(3% 保证金)
|
||||
|
||||
- 震荡 A 级 → medium(6% × 70% ≈ 4%)
|
||||
- 震荡 B 级 → light(3% × 70% ≈ 2%)
|
||||
- 震荡 C 级 → micro(1.5% 保证金)
|
||||
|
||||
- **逆势交易**(仅允许在 medium/weak 趋势 + confidence >= 85):
|
||||
- 逆势 A 级 → light(3% × 30% ≈ 1%)
|
||||
- 逆势 B/C 级 → **禁止**,返回 HOLD
|
||||
|
||||
**示例**:
|
||||
- 账户余额 $10,000
|
||||
- 顺势 heavy:保证金 $1,200 → 持仓 $24,000
|
||||
- 顺势 medium:保证金 $600 → 持仓 $12,000
|
||||
- 顺势 light:保证金 $300 → 持仓 $6,000
|
||||
- 逆势 light(极少数情况):保证金 $100 → 持仓 $2,000
|
||||
|
||||
### 2. 平仓(CLOSE)
|
||||
**时机**:
|
||||
- 触发止损/止盈
|
||||
@ -67,7 +111,24 @@ class TradingDecisionMaker:
|
||||
- 降低风险敞口
|
||||
- 不确定增加
|
||||
|
||||
### 5. 观望(HOLD)
|
||||
### 5. 取消挂单(CANCEL_PENDING)
|
||||
**时机**:
|
||||
- **趋势反转时自动取消反向挂单**(重要):
|
||||
- 当前是 `uptrend`(上升趋势)时,取消所有做空(short)挂单
|
||||
- 当前是 `downtrend`(下降趋势)时,取消所有做多(long)挂单
|
||||
- 趋势强度为 `strong` 时,必须立即取消反向挂单
|
||||
- **信号方向与挂单方向相反**:
|
||||
- 新信号是 buy,但存在 sell 挂单 → 取消 sell 挂单
|
||||
- 新信号是 sell,但存在 buy 挂单 → 取消 buy 挂单
|
||||
- **价格偏离过大**:
|
||||
- 当前价格距离挂单价超过 3%,建议取消重新挂单
|
||||
|
||||
**输出格式**:
|
||||
- `decision: "CANCEL_PENDING"`
|
||||
- `orders_to_cancel`: ["order_id_1", "order_id_2"] - 要取消的订单ID列表
|
||||
- `reasoning`: "取消原因"
|
||||
|
||||
### 6. 观望(HOLD)
|
||||
**时机**:
|
||||
- 信号不明确
|
||||
- 风险过大
|
||||
@ -99,6 +160,7 @@ class TradingDecisionMaker:
|
||||
- **heavy**:使用保证金 = 账户余额 × 12%
|
||||
- **medium**:使用保证金 = 账户余额 × 6%
|
||||
- **light**:使用保证金 = 账户余额 × 3%
|
||||
- **micro**:使用保证金 = 账户余额 × 1.5%(极小仓位,仅用于震荡市或特殊逆势情况)
|
||||
|
||||
#### 4. 选择逻辑示例
|
||||
假设当前可用杠杆空间为 50%:
|
||||
@ -135,21 +197,36 @@ class TradingDecisionMaker:
|
||||
|
||||
```json
|
||||
{
|
||||
"decision": "OPEN/CLOSE/ADD/REDUCE/HOLD",
|
||||
"decision": "OPEN/CLOSE/ADD/REDUCE/CANCEL_PENDING/HOLD",
|
||||
"symbol": "BTC/USDT",
|
||||
"side": "buy/sell",
|
||||
"action": "open_long/close_short/add_long/...",
|
||||
"position_size": "heavy/medium/light",
|
||||
"position_size": "heavy/medium/light/micro",
|
||||
"quantity": 1200,
|
||||
"position_multiplier": 1.0,
|
||||
"confidence": 0-100,
|
||||
"reasoning": "简洁的决策理由(1句话,15字以内)",
|
||||
"risk_analysis": "核心风险点(1句话,15字以内)",
|
||||
"stop_loss": 65500,
|
||||
"take_profit": 67500,
|
||||
"orders_to_cancel": ["order_id_1", "order_id_2"],
|
||||
"notes": "其他说明"
|
||||
}
|
||||
```
|
||||
|
||||
**注意**:
|
||||
- `position_size` 可选值:`heavy`/`medium`/`light`/`micro`
|
||||
- `heavy`:12% 保证金(顺势A级)
|
||||
- `medium`:6% 保证金(顺势B级 或 震荡A级)
|
||||
- `light`:3% 保证金(顺势C级 或 震荡B级)
|
||||
- `micro`:1.5% 保证金(震荡C级 或 特殊逆势情况)
|
||||
- `position_multiplier`:仓位倍率(可选,默认1.0)
|
||||
- 顺势交易:1.0
|
||||
- 震荡市:0.7
|
||||
- 逆势交易(极少数情况):0.3
|
||||
- 如果 `decision` 是 `CANCEL_PENDING`,需要提供 `orders_to_cancel` 字段(要取消的订单ID列表)
|
||||
- 如果 `decision` 是 `OPEN/ADD/CLOSE/REDUCE`,不需要 `orders_to_cancel` 字段
|
||||
|
||||
## 重要说明
|
||||
- **所有价格必须是纯数字**,不要加 $ 符号、逗号或其他格式
|
||||
- `stop_loss`、`take_profit` 必须是数字类型
|
||||
@ -182,7 +259,8 @@ class TradingDecisionMaker:
|
||||
market_signal: Dict[str, Any],
|
||||
positions: List[Dict[str, Any]],
|
||||
account: Dict[str, Any],
|
||||
current_price: float = None) -> Dict[str, Any]:
|
||||
current_price: float = None,
|
||||
pending_orders: List[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
做出交易决策
|
||||
|
||||
@ -191,6 +269,7 @@ class TradingDecisionMaker:
|
||||
positions: 当前持仓列表
|
||||
account: 账户状态
|
||||
current_price: 当前价格(用于判断入场方式)
|
||||
pending_orders: 未成交的挂单列表
|
||||
|
||||
Returns:
|
||||
交易决策字典
|
||||
@ -198,7 +277,7 @@ class TradingDecisionMaker:
|
||||
try:
|
||||
# 1. 准备决策上下文
|
||||
decision_context = self._prepare_decision_context(
|
||||
market_signal, positions, account, current_price
|
||||
market_signal, positions, account, current_price, pending_orders or []
|
||||
)
|
||||
|
||||
# 2. 构建提示词
|
||||
@ -229,15 +308,19 @@ class TradingDecisionMaker:
|
||||
market_signal: Dict[str, Any],
|
||||
positions: List[Dict[str, Any]],
|
||||
account: Dict[str, Any],
|
||||
current_price: float = None) -> Dict[str, Any]:
|
||||
current_price: float = None,
|
||||
pending_orders: List[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
"""准备决策上下文"""
|
||||
context = {
|
||||
'symbol': market_signal.get('symbol'),
|
||||
'market_state': market_signal.get('market_state'),
|
||||
'trend': market_signal.get('trend'),
|
||||
'trend_direction': market_signal.get('trend_direction'), # 新增:趋势方向
|
||||
'trend_strength': market_signal.get('trend_strength'), # 新增:趋势强度
|
||||
'signals': market_signal.get('signals', []),
|
||||
'key_levels': market_signal.get('key_levels', {}),
|
||||
'positions': positions,
|
||||
'pending_orders': pending_orders or [], # 新增:挂单列表
|
||||
'account': account,
|
||||
'current_price': current_price
|
||||
}
|
||||
@ -281,7 +364,13 @@ class TradingDecisionMaker:
|
||||
prompt_parts.append(f"## 市场信号")
|
||||
prompt_parts.append(f"交易对: {context['symbol']}")
|
||||
prompt_parts.append(f"市场状态: {context.get('market_state')}")
|
||||
prompt_parts.append(f"趋势: {context.get('trend')}")
|
||||
|
||||
# 趋势信息(新增)
|
||||
trend_direction = context.get('trend_direction', 'neutral')
|
||||
trend_strength = context.get('trend_strength', 'weak')
|
||||
direction_text = {'uptrend': '📈 上升趋势', 'downtrend': '📉 下降趋势', 'neutral': '➖ 震荡'}.get(trend_direction, trend_direction)
|
||||
strength_text = {'strong': '强势', 'medium': '中等', 'weak': '弱势'}.get(trend_strength, trend_strength)
|
||||
prompt_parts.append(f"趋势: {direction_text} ({strength_text})")
|
||||
|
||||
# 当前价格(如果有)
|
||||
current_price = context.get('current_price')
|
||||
@ -306,6 +395,36 @@ class TradingDecisionMaker:
|
||||
|
||||
prompt_parts.append(f" 理由: {sig.get('reasoning', 'N/A')}")
|
||||
|
||||
# 趋势一致性检查(新增)
|
||||
trend_direction = context.get('trend_direction', 'neutral')
|
||||
trend_strength = context.get('trend_strength', 'weak')
|
||||
prompt_parts.append(f"\n## 🚨 趋势一致性检查(第一优先级)")
|
||||
|
||||
for i, sig in enumerate(signals, 1):
|
||||
action = sig.get('action', 'hold')
|
||||
is_aligned = (trend_direction == 'uptrend' and action == 'buy') or \
|
||||
(trend_direction == 'downtrend' and action == 'sell') or \
|
||||
(trend_direction == 'neutral')
|
||||
|
||||
if is_aligned:
|
||||
prompt_parts.append(f"✅ 信号#{i} ({action}) 与趋势 ({trend_direction}) 一致 → 可正常开仓")
|
||||
else:
|
||||
# 逆势信号
|
||||
if trend_strength == 'strong':
|
||||
prompt_parts.append(f"❌ 信号#{i} ({action}) 与强趋势 ({trend_direction}) 相反 → **严禁逆势,返回 HOLD**")
|
||||
elif trend_strength == 'medium':
|
||||
confidence = sig.get('confidence', 0)
|
||||
if confidence >= 85:
|
||||
prompt_parts.append(f"⚠️ 信号#{i} ({action}) 与中等趋势相反,但 confidence={confidence}>=85 → 可谨慎 micro 仓位")
|
||||
else:
|
||||
prompt_parts.append(f"❌ 信号#{i} ({action}) 与中等趋势相反,confidence不足 → 返回 HOLD")
|
||||
else: # weak or neutral
|
||||
confidence = sig.get('confidence', 0)
|
||||
if confidence >= 85:
|
||||
prompt_parts.append(f"⚠️ 信号#{i} ({action}) 与弱趋势相反,但 confidence={confidence}>=85 → 可 micro 仓位")
|
||||
else:
|
||||
prompt_parts.append(f"❌ 信号#{i} ({action}) 与弱趋势相反,confidence不足 → 返回 HOLD")
|
||||
|
||||
# 关键价位
|
||||
key_levels = context.get('key_levels', {})
|
||||
if key_levels:
|
||||
@ -353,6 +472,19 @@ class TradingDecisionMaker:
|
||||
else:
|
||||
prompt_parts.append("无持仓")
|
||||
|
||||
# 当前挂单
|
||||
pending_orders = context.get('pending_orders', [])
|
||||
prompt_parts.append(f"\n## 当前挂单")
|
||||
if pending_orders:
|
||||
for order in pending_orders:
|
||||
side_icon = "🟢" if order.get('side') == 'long' else "🔴"
|
||||
entry_type = "现价单" if order.get('entry_type') == 'market' else "挂单"
|
||||
prompt_parts.append(f"- {side_icon} {order.get('symbol')}: {order.get('side')} | {entry_type}")
|
||||
prompt_parts.append(f" 挂单价: ${order.get('entry_price')} | 数量: {order.get('quantity')} USDT")
|
||||
prompt_parts.append(f" 订单ID: {order.get('order_id')}")
|
||||
else:
|
||||
prompt_parts.append("无挂单")
|
||||
|
||||
# 账户状态
|
||||
account = context.get('account', {})
|
||||
lev_info = context.get('leverage_info', {})
|
||||
|
||||
@ -1000,6 +1000,44 @@ class PaperTradingService:
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def cancel_order(self, order_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
取消挂单
|
||||
|
||||
Args:
|
||||
order_id: 订单ID
|
||||
|
||||
Returns:
|
||||
取消结果字典
|
||||
"""
|
||||
if order_id not in self.active_orders:
|
||||
return {
|
||||
'success': False,
|
||||
'message': f'订单不存在: {order_id}'
|
||||
}
|
||||
|
||||
order = self.active_orders[order_id]
|
||||
|
||||
# 只能取消挂单状态的订单
|
||||
if order.status != OrderStatus.PENDING:
|
||||
return {
|
||||
'success': False,
|
||||
'message': f'只能取消挂单状态的订单,当前状态: {order.status.value}'
|
||||
}
|
||||
|
||||
result = self._cancel_pending_order(order)
|
||||
if result:
|
||||
return {
|
||||
'success': True,
|
||||
'order_id': order_id,
|
||||
'message': '挂单已取消'
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'success': False,
|
||||
'message': '取消挂单失败'
|
||||
}
|
||||
|
||||
def _cancel_pending_order(self, order: PaperOrder) -> Dict[str, Any]:
|
||||
"""取消挂单"""
|
||||
db = db_service.get_session()
|
||||
|
||||
@ -553,6 +553,89 @@ class RealTradingService:
|
||||
return order.to_dict()
|
||||
return None
|
||||
|
||||
def cancel_order(self, order_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
取消挂单
|
||||
|
||||
Args:
|
||||
order_id: 订单ID
|
||||
|
||||
Returns:
|
||||
取消结果字典
|
||||
"""
|
||||
if order_id not in self.active_orders:
|
||||
return {
|
||||
'success': False,
|
||||
'message': f'订单不存在: {order_id}'
|
||||
}
|
||||
|
||||
order = self.active_orders[order_id]
|
||||
|
||||
# 只能取消挂单状态的订单
|
||||
if order.status != OrderStatus.PENDING:
|
||||
return {
|
||||
'success': False,
|
||||
'message': f'只能取消挂单状态的订单,当前状态: {order.status.value}'
|
||||
}
|
||||
|
||||
# 调用交易所 API 取消订单
|
||||
if not self.trading_api:
|
||||
return {
|
||||
'success': False,
|
||||
'message': '交易 API 未初始化'
|
||||
}
|
||||
|
||||
try:
|
||||
# 获取原始订单ID(如果有)
|
||||
original_order_id = order.original_order_id or order_id
|
||||
|
||||
# 调用交易所取消订单API
|
||||
success = self.trading_api.cancel_order(symbol=order.symbol, order_id=original_order_id)
|
||||
|
||||
if success:
|
||||
# 更新本地订单状态
|
||||
order.status = OrderStatus.CANCELLED
|
||||
order.closed_at = datetime.utcnow()
|
||||
|
||||
# 保存到数据库
|
||||
db = db_service.get_session()
|
||||
try:
|
||||
db_order = db.query(RealOrder).filter(RealOrder.order_id == order_id).first()
|
||||
if db_order:
|
||||
db_order.status = OrderStatus.CANCELLED
|
||||
db_order.closed_at = datetime.utcnow()
|
||||
db.merge(db_order)
|
||||
db.commit()
|
||||
except Exception as e:
|
||||
logger.error(f"更新数据库订单状态失败: {e}")
|
||||
db.rollback()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
# 从活跃订单缓存中移除
|
||||
if order_id in self.active_orders:
|
||||
del self.active_orders[order_id]
|
||||
|
||||
logger.info(f"实盘挂单已取消: {order_id} | {order.symbol}")
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'order_id': order_id,
|
||||
'message': '挂单已取消'
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'success': False,
|
||||
'message': '交易所取消订单失败'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"取消实盘挂单失败: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'message': f'取消订单异常: {e}'
|
||||
}
|
||||
|
||||
def sync_positions_from_exchange(self) -> List[Dict]:
|
||||
"""
|
||||
从交易所同步持仓状态
|
||||
|
||||
142
backend/test_market_signal.py
Normal file
142
backend/test_market_signal.py
Normal file
@ -0,0 +1,142 @@
|
||||
"""
|
||||
测试市场信号分析 - 验证趋势判断功能
|
||||
"""
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# 添加项目路径
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
from app.crypto_agent.market_signal_analyzer import MarketSignalAnalyzer
|
||||
from app.utils.logger import logger
|
||||
from app.services.bitget_service import BitgetService
|
||||
|
||||
|
||||
async def test_trend_analysis(symbol: str = "BTCUSDT"):
|
||||
"""测试趋势分析"""
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info(f"开始测试市场信号分析: {symbol}")
|
||||
logger.info("=" * 60)
|
||||
|
||||
# 1. 获取 K 线数据
|
||||
logger.info(f"\n[1/3] 获取 {symbol} 的 K 线数据...")
|
||||
bitget_service = BitgetService()
|
||||
data = bitget_service.get_multi_timeframe_data(symbol)
|
||||
|
||||
if not data:
|
||||
logger.error(f"❌ 无法获取 {symbol} 的 K 线数据")
|
||||
return
|
||||
|
||||
logger.info(f"✅ K 线数据获取成功")
|
||||
for tf, df in data.items():
|
||||
if df is not None and len(df) > 0:
|
||||
latest_price = float(df.iloc[-1]['close'])
|
||||
logger.info(f" {tf}: 最新价格 ${latest_price:,.2f}, 数据量 {len(df)} 条")
|
||||
|
||||
# 2. 分析市场信号
|
||||
logger.info(f"\n[2/3] 分析市场信号...")
|
||||
analyzer = MarketSignalAnalyzer()
|
||||
result = await analyzer.analyze(symbol, data)
|
||||
|
||||
# 3. 打印分析结果
|
||||
logger.info(f"\n[3/3] 分析结果:")
|
||||
logger.info("-" * 60)
|
||||
|
||||
# 打印趋势判断(新功能)
|
||||
logger.info("\n📊 趋势判断(新增):")
|
||||
logger.info(f" 趋势方向: {result.get('trend_direction', 'unknown')}")
|
||||
logger.info(f" 趋势强度: {result.get('trend_strength', 'unknown')}")
|
||||
|
||||
# 打印市场状态
|
||||
logger.info(f"\n📈 市场状态:")
|
||||
logger.info(f" {result.get('market_state', 'unknown')}")
|
||||
logger.info(f" 分析摘要: {result.get('analysis_summary', 'unknown')}")
|
||||
logger.info(f" 量价分析: {result.get('volume_analysis', 'unknown')}")
|
||||
|
||||
# 打印关键价位
|
||||
if result.get('key_levels'):
|
||||
logger.info(f"\n💰 关键价位:")
|
||||
levels = result['key_levels']
|
||||
if levels.get('support'):
|
||||
logger.info(f" 支撑位: {[f'${s:,.0f}' for s in levels['support'] if s]}")
|
||||
if levels.get('resistance'):
|
||||
logger.info(f" 阻力位: {[f'${r:,.0f}' for r in levels['resistance'] if r]}")
|
||||
|
||||
# 打印信号
|
||||
logger.info(f"\n🎯 交易信号:")
|
||||
signals = result.get('signals', [])
|
||||
if not signals:
|
||||
logger.warning(" ⚠️ 无交易信号(观望)")
|
||||
else:
|
||||
for i, sig in enumerate(signals, 1):
|
||||
action_emoji = "🟢" if sig.get('action') == 'buy' else "🔴" if sig.get('action') == 'sell' else "⏪"
|
||||
grade_icon = {'A': '⭐⭐⭐', 'B': '⭐⭐', 'C': '⭐', 'D': ''}.get(sig.get('grade', ''), '')
|
||||
entry_type_emoji = "⚡现价" if sig.get('entry_type') == 'market' else "⏳挂单"
|
||||
|
||||
logger.info(f"\n 信号 #{i} {action_emoji} {grade_icon}")
|
||||
logger.info(f" ├─ 类型: {sig.get('timeframe', 'unknown')} | {entry_type_emoji}")
|
||||
logger.info(f" ├─ 操作: {sig.get('action', 'unknown').upper()}")
|
||||
logger.info(f" ├─ 信心度: {sig.get('confidence', 0)}% | 等级: {sig.get('grade', 'unknown')}")
|
||||
logger.info(f" ├─ 入场价: ${sig.get('entry_zone', 0):,.2f}")
|
||||
logger.info(f" ├─ 止损: ${sig.get('stop_loss', 0):,.2f} | 止盈: ${sig.get('take_profit', 0):,.2f}")
|
||||
logger.info(f" └─ 理由: {sig.get('reasoning', 'unknown')}")
|
||||
|
||||
# 检查是否顺势
|
||||
trend_dir = result.get('trend_direction', 'neutral')
|
||||
action = sig.get('action', '')
|
||||
if trend_dir == 'uptrend' and action == 'sell':
|
||||
logger.warning(f" ⚠️ 警告: 上升趋势中做空(逆势)")
|
||||
elif trend_dir == 'downtrend' and action == 'buy':
|
||||
logger.warning(f" ⚠️ 警告: 下降趋势中做多(逆势)")
|
||||
elif trend_dir == 'uptrend' and action == 'buy':
|
||||
logger.info(f" ✅ 顺势交易")
|
||||
elif trend_dir == 'downtrend' and action == 'sell':
|
||||
logger.info(f" ✅ 顺势交易")
|
||||
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info("测试完成")
|
||||
logger.info("=" * 60)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def test_multiple_symbols():
|
||||
"""测试多个交易对"""
|
||||
symbols = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT']
|
||||
|
||||
for symbol in symbols:
|
||||
logger.info(f"\n\n{'#' * 60}")
|
||||
logger.info(f"# 测试 {symbol}")
|
||||
logger.info(f"{'#' * 60}")
|
||||
|
||||
try:
|
||||
await test_trend_analysis(symbol)
|
||||
except Exception as e:
|
||||
logger.error(f"❌ {symbol} 测试失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# 等待一段时间,避免 API 限流
|
||||
await asyncio.sleep(2)
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='测试市场信号分析')
|
||||
parser.add_argument('--symbol', default='BTCUSDT', help='交易对(默认: BTCUSDT)')
|
||||
parser.add_argument('--multi', action='store_true', help='测试多个交易对')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.multi:
|
||||
asyncio.run(test_multiple_symbols())
|
||||
else:
|
||||
asyncio.run(test_trend_analysis(args.symbol))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
Reference in New Issue
Block a user