优化顺势而为策略
This commit is contained in:
parent
faa551e661
commit
f6e15d4a48
@ -514,9 +514,9 @@ class CryptoAgent:
|
|||||||
# 模拟交易决策
|
# 模拟交易决策
|
||||||
if paper_trading_enabled:
|
if paper_trading_enabled:
|
||||||
logger.info(f"\n📊 【模拟交易决策】")
|
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(
|
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)
|
self._log_trading_decision(paper_decision)
|
||||||
else:
|
else:
|
||||||
@ -527,9 +527,9 @@ class CryptoAgent:
|
|||||||
logger.info(f"\n💰 【实盘交易决策】")
|
logger.info(f"\n💰 【实盘交易决策】")
|
||||||
# 检查是否开启自动交易
|
# 检查是否开启自动交易
|
||||||
if self.real_trading and self.real_trading.get_auto_trading_status():
|
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(
|
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)
|
self._log_trading_decision(real_decision)
|
||||||
else:
|
else:
|
||||||
@ -657,6 +657,9 @@ class CryptoAgent:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
use_real_trading: True 获取实盘状态,False 获取模拟交易状态
|
use_real_trading: True 获取实盘状态,False 获取模拟交易状态
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(positions, account, pending_orders) - 持仓列表、账户状态、挂单列表
|
||||||
"""
|
"""
|
||||||
if use_real_trading and self.real_trading:
|
if use_real_trading and self.real_trading:
|
||||||
# 实盘交易
|
# 实盘交易
|
||||||
@ -667,21 +670,33 @@ class CryptoAgent:
|
|||||||
active_orders = self.paper_trading.get_active_orders()
|
active_orders = self.paper_trading.get_active_orders()
|
||||||
account = self.paper_trading.get_account_status()
|
account = self.paper_trading.get_account_status()
|
||||||
|
|
||||||
# 转换为标准格式(只返回已成交的订单作为持仓)
|
# 分离持仓和挂单
|
||||||
position_list = []
|
position_list = []
|
||||||
|
pending_orders = []
|
||||||
for order in active_orders:
|
for order in active_orders:
|
||||||
# 只返回已成交(OPEN 状态且有 filled_price)的订单
|
|
||||||
if order.get('status') == 'open' and order.get('filled_price'):
|
if order.get('status') == 'open' and order.get('filled_price'):
|
||||||
|
# 已成交的订单作为持仓
|
||||||
position_list.append({
|
position_list.append({
|
||||||
'symbol': order.get('symbol'),
|
'symbol': order.get('symbol'),
|
||||||
'side': order.get('side'),
|
'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'),
|
'entry_price': order.get('filled_price') or order.get('entry_price'),
|
||||||
'stop_loss': order.get('stop_loss'),
|
'stop_loss': order.get('stop_loss'),
|
||||||
'take_profit': order.get('take_profit')
|
'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],
|
async def _execute_decisions(self, paper_decision: Dict[str, Any],
|
||||||
real_decision: Dict[str, Any],
|
real_decision: Dict[str, Any],
|
||||||
@ -737,6 +752,12 @@ class CryptoAgent:
|
|||||||
elif decision_type == 'CLOSE':
|
elif decision_type == 'CLOSE':
|
||||||
await self._execute_close(paper_decision, paper_trading=True)
|
await self._execute_close(paper_decision, paper_trading=True)
|
||||||
paper_executed = 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':
|
elif decision_type == 'CLOSE':
|
||||||
await self._execute_close(real_decision, paper_trading=False)
|
await self._execute_close(real_decision, paper_trading=False)
|
||||||
real_executed = True
|
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:
|
if not paper_executed and not real_executed:
|
||||||
@ -1260,6 +1287,76 @@ class CryptoAgent:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"执行平仓失败: {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],
|
def _convert_to_paper_signal(self, symbol: str, signal: Dict[str, Any],
|
||||||
current_price: float) -> Dict[str, Any]:
|
current_price: float) -> Dict[str, Any]:
|
||||||
"""转换 LLM 信号格式为模拟交易格式"""
|
"""转换 LLM 信号格式为模拟交易格式"""
|
||||||
|
|||||||
@ -33,6 +33,22 @@ class TradingDecisionMaker:
|
|||||||
3. 账户状态(余额、已用保证金、杠杆等)
|
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)
|
### 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)
|
### 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%
|
- **heavy**:使用保证金 = 账户余额 × 12%
|
||||||
- **medium**:使用保证金 = 账户余额 × 6%
|
- **medium**:使用保证金 = 账户余额 × 6%
|
||||||
- **light**:使用保证金 = 账户余额 × 3%
|
- **light**:使用保证金 = 账户余额 × 3%
|
||||||
|
- **micro**:使用保证金 = 账户余额 × 1.5%(极小仓位,仅用于震荡市或特殊逆势情况)
|
||||||
|
|
||||||
#### 4. 选择逻辑示例
|
#### 4. 选择逻辑示例
|
||||||
假设当前可用杠杆空间为 50%:
|
假设当前可用杠杆空间为 50%:
|
||||||
@ -135,21 +197,36 @@ class TradingDecisionMaker:
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"decision": "OPEN/CLOSE/ADD/REDUCE/HOLD",
|
"decision": "OPEN/CLOSE/ADD/REDUCE/CANCEL_PENDING/HOLD",
|
||||||
"symbol": "BTC/USDT",
|
"symbol": "BTC/USDT",
|
||||||
"side": "buy/sell",
|
"side": "buy/sell",
|
||||||
"action": "open_long/close_short/add_long/...",
|
"action": "open_long/close_short/add_long/...",
|
||||||
"position_size": "heavy/medium/light",
|
"position_size": "heavy/medium/light/micro",
|
||||||
"quantity": 1200,
|
"quantity": 1200,
|
||||||
|
"position_multiplier": 1.0,
|
||||||
"confidence": 0-100,
|
"confidence": 0-100,
|
||||||
"reasoning": "简洁的决策理由(1句话,15字以内)",
|
"reasoning": "简洁的决策理由(1句话,15字以内)",
|
||||||
"risk_analysis": "核心风险点(1句话,15字以内)",
|
"risk_analysis": "核心风险点(1句话,15字以内)",
|
||||||
"stop_loss": 65500,
|
"stop_loss": 65500,
|
||||||
"take_profit": 67500,
|
"take_profit": 67500,
|
||||||
|
"orders_to_cancel": ["order_id_1", "order_id_2"],
|
||||||
"notes": "其他说明"
|
"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` 必须是数字类型
|
- `stop_loss`、`take_profit` 必须是数字类型
|
||||||
@ -182,7 +259,8 @@ class TradingDecisionMaker:
|
|||||||
market_signal: Dict[str, Any],
|
market_signal: Dict[str, Any],
|
||||||
positions: List[Dict[str, Any]],
|
positions: List[Dict[str, Any]],
|
||||||
account: 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: 当前持仓列表
|
positions: 当前持仓列表
|
||||||
account: 账户状态
|
account: 账户状态
|
||||||
current_price: 当前价格(用于判断入场方式)
|
current_price: 当前价格(用于判断入场方式)
|
||||||
|
pending_orders: 未成交的挂单列表
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
交易决策字典
|
交易决策字典
|
||||||
@ -198,7 +277,7 @@ class TradingDecisionMaker:
|
|||||||
try:
|
try:
|
||||||
# 1. 准备决策上下文
|
# 1. 准备决策上下文
|
||||||
decision_context = self._prepare_decision_context(
|
decision_context = self._prepare_decision_context(
|
||||||
market_signal, positions, account, current_price
|
market_signal, positions, account, current_price, pending_orders or []
|
||||||
)
|
)
|
||||||
|
|
||||||
# 2. 构建提示词
|
# 2. 构建提示词
|
||||||
@ -229,15 +308,19 @@ class TradingDecisionMaker:
|
|||||||
market_signal: Dict[str, Any],
|
market_signal: Dict[str, Any],
|
||||||
positions: List[Dict[str, Any]],
|
positions: List[Dict[str, Any]],
|
||||||
account: 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 = {
|
context = {
|
||||||
'symbol': market_signal.get('symbol'),
|
'symbol': market_signal.get('symbol'),
|
||||||
'market_state': market_signal.get('market_state'),
|
'market_state': market_signal.get('market_state'),
|
||||||
'trend': market_signal.get('trend'),
|
'trend': market_signal.get('trend'),
|
||||||
|
'trend_direction': market_signal.get('trend_direction'), # 新增:趋势方向
|
||||||
|
'trend_strength': market_signal.get('trend_strength'), # 新增:趋势强度
|
||||||
'signals': market_signal.get('signals', []),
|
'signals': market_signal.get('signals', []),
|
||||||
'key_levels': market_signal.get('key_levels', {}),
|
'key_levels': market_signal.get('key_levels', {}),
|
||||||
'positions': positions,
|
'positions': positions,
|
||||||
|
'pending_orders': pending_orders or [], # 新增:挂单列表
|
||||||
'account': account,
|
'account': account,
|
||||||
'current_price': current_price
|
'current_price': current_price
|
||||||
}
|
}
|
||||||
@ -281,7 +364,13 @@ class TradingDecisionMaker:
|
|||||||
prompt_parts.append(f"## 市场信号")
|
prompt_parts.append(f"## 市场信号")
|
||||||
prompt_parts.append(f"交易对: {context['symbol']}")
|
prompt_parts.append(f"交易对: {context['symbol']}")
|
||||||
prompt_parts.append(f"市场状态: {context.get('market_state')}")
|
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')
|
current_price = context.get('current_price')
|
||||||
@ -306,6 +395,36 @@ class TradingDecisionMaker:
|
|||||||
|
|
||||||
prompt_parts.append(f" 理由: {sig.get('reasoning', 'N/A')}")
|
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', {})
|
key_levels = context.get('key_levels', {})
|
||||||
if key_levels:
|
if key_levels:
|
||||||
@ -353,6 +472,19 @@ class TradingDecisionMaker:
|
|||||||
else:
|
else:
|
||||||
prompt_parts.append("无持仓")
|
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', {})
|
account = context.get('account', {})
|
||||||
lev_info = context.get('leverage_info', {})
|
lev_info = context.get('leverage_info', {})
|
||||||
|
|||||||
@ -1000,6 +1000,44 @@ class PaperTradingService:
|
|||||||
finally:
|
finally:
|
||||||
db.close()
|
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]:
|
def _cancel_pending_order(self, order: PaperOrder) -> Dict[str, Any]:
|
||||||
"""取消挂单"""
|
"""取消挂单"""
|
||||||
db = db_service.get_session()
|
db = db_service.get_session()
|
||||||
|
|||||||
@ -553,6 +553,89 @@ class RealTradingService:
|
|||||||
return order.to_dict()
|
return order.to_dict()
|
||||||
return None
|
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]:
|
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