update
This commit is contained in:
parent
44fc91dfc2
commit
f31322a2a5
@ -64,6 +64,24 @@ class CryptoAgent:
|
||||
}
|
||||
}
|
||||
|
||||
PLATFORM_SIGNAL_PRIORITY = {
|
||||
'PaperTrading': ['short_term', 'medium_term'],
|
||||
'Hyperliquid': ['short_term', 'medium_term'],
|
||||
'Bitget': ['medium_term', 'short_term'],
|
||||
}
|
||||
|
||||
SIGNAL_POSITION_SIZE_DEFAULTS = {
|
||||
'short_term': 'light',
|
||||
'medium_term': 'medium',
|
||||
'long_term': 'medium',
|
||||
}
|
||||
|
||||
SIGNAL_MARGIN_MULTIPLIERS = {
|
||||
'short_term': 0.85,
|
||||
'medium_term': 1.0,
|
||||
'long_term': 1.0,
|
||||
}
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
"""单例模式 - 确保只有一个实例"""
|
||||
if cls._instance is None:
|
||||
@ -636,34 +654,9 @@ class CryptoAgent:
|
||||
# ============================================================
|
||||
logger.info(f"\n🤖 【第一阶段:市场信号分析】")
|
||||
|
||||
# 获取上一轮的信号(用于上下文)
|
||||
previous_signal = self.last_signals.get(symbol)
|
||||
|
||||
# 显示上一轮信号(如果有)
|
||||
if previous_signal:
|
||||
prev_time = previous_signal.get('timestamp', 'Unknown')
|
||||
prev_trend = previous_signal.get('trend', 'Unknown')
|
||||
prev_signals = previous_signal.get('signals', [])
|
||||
logger.info(f"📋 上一轮分析时间: {prev_time}")
|
||||
logger.info(f"📋 上一轮趋势: {prev_trend}")
|
||||
|
||||
if prev_signals:
|
||||
for sig in prev_signals:
|
||||
action = sig.get('action', 'N/A')
|
||||
confidence = sig.get('confidence', 0)
|
||||
timeframe = sig.get('timeframe', 'unknown')
|
||||
type_map = {'short_term': '短线', 'medium_term': '中线', 'long_term': '长线'}
|
||||
type_text = type_map.get(timeframe, timeframe)
|
||||
logger.info(f"📋 上一轮信号: {type_text} | {action} | {confidence}%")
|
||||
else:
|
||||
logger.info(f"📋 上一轮信号: 无交易信号(观望)")
|
||||
else:
|
||||
logger.info(f"📋 上一轮信号: 无历史记录(首次分析)")
|
||||
|
||||
market_signal = await self.market_analyzer.analyze(
|
||||
symbol, data,
|
||||
symbols=self.symbols,
|
||||
previous_signal=previous_signal
|
||||
symbols=self.symbols
|
||||
)
|
||||
|
||||
# 输出市场分析结果
|
||||
@ -696,6 +689,11 @@ class CryptoAgent:
|
||||
return
|
||||
|
||||
logger.info(f"\n✅ 发现 {len(valid_signals)} 个有效交易信号(达到 {threshold}% 阈值)")
|
||||
for signal in valid_signals:
|
||||
logger.info(
|
||||
f" - {signal.get('timeframe', signal.get('type', 'unknown'))} | "
|
||||
f"{signal.get('action')} | {signal.get('confidence', 0)}%"
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# 发送市场信号通知(独立于交易决策)
|
||||
@ -707,34 +705,26 @@ class CryptoAgent:
|
||||
# ============================================================
|
||||
logger.info(f"\n🤖 【第二阶段:各平台独立处理信号】")
|
||||
|
||||
# 使用第一个有效信号
|
||||
main_signal = valid_signals[0]
|
||||
signal_action = main_signal.get('action') # buy/sell
|
||||
|
||||
# 构建标准信号格式
|
||||
trading_signal = {
|
||||
'symbol': symbol,
|
||||
'action': signal_action,
|
||||
'confidence': main_signal.get('confidence', 50),
|
||||
'grade': main_signal.get('grade', 'C'), # 添加信号等级
|
||||
'entry_price': main_signal.get('entry_price', current_price),
|
||||
'stop_loss': main_signal.get('stop_loss'),
|
||||
'take_profit': main_signal.get('take_profit'),
|
||||
'reasoning': main_signal.get('reasoning', ''),
|
||||
}
|
||||
|
||||
logger.info(f" 信号: {signal_action} {symbol} @ ${trading_signal['entry_price']:.2f} (置信度 {trading_signal['confidence']}%, {trading_signal['grade']}级)")
|
||||
|
||||
# 2.1 模拟盘处理
|
||||
if self.settings.paper_trading_enabled:
|
||||
logger.info(f"\n📊 【模拟盘】")
|
||||
paper_positions, paper_account, paper_pending = self._get_paper_trading_state()
|
||||
paper_decision = self.execute_signal_with_rules(
|
||||
trading_signal, 'PaperTrading', paper_account, paper_positions, paper_pending
|
||||
)
|
||||
paper_decision = self._normalize_execution_decision(
|
||||
paper_decision, paper_positions, paper_pending
|
||||
)
|
||||
paper_signal = self._select_signal_for_platform(valid_signals, 'PaperTrading')
|
||||
if paper_signal:
|
||||
logger.info(
|
||||
f" 采用信号: {paper_signal.get('timeframe', 'unknown')} | "
|
||||
f"{paper_signal.get('action')} | {paper_signal.get('confidence', 0)}%"
|
||||
)
|
||||
trading_signal = self._build_execution_signal(symbol, paper_signal, current_price)
|
||||
paper_decision = self.execute_signal_with_rules(
|
||||
trading_signal, 'PaperTrading', paper_account, paper_positions, paper_pending
|
||||
)
|
||||
paper_decision = self._normalize_execution_decision(
|
||||
paper_decision, paper_positions, paper_pending
|
||||
)
|
||||
else:
|
||||
logger.info(" 无可执行信号")
|
||||
paper_decision = {"decision": "HOLD", "action": "IGNORE", "reason": "无适配信号", "reasoning": "无适配信号"}
|
||||
# 不发送决策通知(因为是基于硬编码规则的执行,不是 LLM 决策)
|
||||
# await self._send_trading_decision_notification(
|
||||
# paper_decision, market_signal, current_price, prefix="[模拟盘]"
|
||||
@ -747,12 +737,22 @@ class CryptoAgent:
|
||||
if self.hyperliquid:
|
||||
logger.info(f"\n🔥 【Hyperliquid】")
|
||||
hl_positions, hl_account, hl_pending = self._get_hyperliquid_trading_state()
|
||||
hl_decision = self.execute_signal_with_rules(
|
||||
trading_signal, 'Hyperliquid', hl_account, hl_positions, hl_pending
|
||||
)
|
||||
hl_decision = self._normalize_execution_decision(
|
||||
hl_decision, hl_positions, hl_pending
|
||||
)
|
||||
hl_signal = self._select_signal_for_platform(valid_signals, 'Hyperliquid')
|
||||
if hl_signal:
|
||||
logger.info(
|
||||
f" 采用信号: {hl_signal.get('timeframe', 'unknown')} | "
|
||||
f"{hl_signal.get('action')} | {hl_signal.get('confidence', 0)}%"
|
||||
)
|
||||
trading_signal = self._build_execution_signal(symbol, hl_signal, current_price)
|
||||
hl_decision = self.execute_signal_with_rules(
|
||||
trading_signal, 'Hyperliquid', hl_account, hl_positions, hl_pending
|
||||
)
|
||||
hl_decision = self._normalize_execution_decision(
|
||||
hl_decision, hl_positions, hl_pending
|
||||
)
|
||||
else:
|
||||
logger.info(" 无可执行信号")
|
||||
hl_decision = {"decision": "HOLD", "action": "IGNORE", "reason": "无适配信号", "reasoning": "无适配信号"}
|
||||
# 不发送决策通知(因为是基于硬编码规则的执行,不是 LLM 决策)
|
||||
# await self._send_trading_decision_notification(
|
||||
# hl_decision, market_signal, current_price, prefix="[Hyperliquid]"
|
||||
@ -765,12 +765,22 @@ class CryptoAgent:
|
||||
if self.bitget:
|
||||
logger.info(f"\n🔥 【Bitget】")
|
||||
bg_positions, bg_account, bg_pending = self._get_bitget_trading_state()
|
||||
bg_decision = self.execute_signal_with_rules(
|
||||
trading_signal, 'Bitget', bg_account, bg_positions, bg_pending
|
||||
)
|
||||
bg_decision = self._normalize_execution_decision(
|
||||
bg_decision, bg_positions, bg_pending
|
||||
)
|
||||
bg_signal = self._select_signal_for_platform(valid_signals, 'Bitget')
|
||||
if bg_signal:
|
||||
logger.info(
|
||||
f" 采用信号: {bg_signal.get('timeframe', 'unknown')} | "
|
||||
f"{bg_signal.get('action')} | {bg_signal.get('confidence', 0)}%"
|
||||
)
|
||||
trading_signal = self._build_execution_signal(symbol, bg_signal, current_price)
|
||||
bg_decision = self.execute_signal_with_rules(
|
||||
trading_signal, 'Bitget', bg_account, bg_positions, bg_pending
|
||||
)
|
||||
bg_decision = self._normalize_execution_decision(
|
||||
bg_decision, bg_positions, bg_pending
|
||||
)
|
||||
else:
|
||||
logger.info(" 无可执行信号")
|
||||
bg_decision = {"decision": "HOLD", "action": "IGNORE", "reason": "无适配信号", "reasoning": "无适配信号"}
|
||||
# 不发送决策通知(因为是基于硬编码规则的执行,不是 LLM 决策)
|
||||
# await self._send_trading_decision_notification(
|
||||
# bg_decision, market_signal, current_price, prefix="[Bitget]"
|
||||
@ -1118,14 +1128,19 @@ class CryptoAgent:
|
||||
bitget_decision: Dict[str, Any],
|
||||
market_signal: Dict[str, Any], current_price: float):
|
||||
"""执行交易决策(三轨独立)"""
|
||||
# 选择最佳信号用于保存
|
||||
best_signal = self._get_best_signal_from_market(market_signal)
|
||||
# 保存本轮所有达到阈值的可交易信号,避免分流后只落一条信号
|
||||
threshold = self.settings.crypto_llm_threshold * 100
|
||||
symbol = market_signal.get('symbol')
|
||||
signals = market_signal.get('signals', [])
|
||||
valid_signals = [
|
||||
signal for signal in signals
|
||||
if signal.get('action') in {'buy', 'sell'} and signal.get('confidence', 0) >= threshold
|
||||
]
|
||||
|
||||
# 保存信号到数据库(只保存一次)
|
||||
if best_signal:
|
||||
signal_to_save = best_signal.copy()
|
||||
for signal in valid_signals:
|
||||
signal_to_save = signal.copy()
|
||||
signal_to_save['signal_type'] = 'crypto'
|
||||
signal_to_save['symbol'] = market_signal.get('symbol')
|
||||
signal_to_save['symbol'] = symbol
|
||||
signal_to_save['current_price'] = current_price
|
||||
self.signal_db.add_signal(signal_to_save)
|
||||
|
||||
@ -1235,6 +1250,64 @@ class CryptoAgent:
|
||||
sorted_signals = sorted(signals, key=lambda x: x.get('confidence', 0), reverse=True)
|
||||
return sorted_signals[0]
|
||||
|
||||
def _select_signal_for_platform(self, signals: List[Dict[str, Any]], platform_name: str) -> Optional[Dict[str, Any]]:
|
||||
"""根据平台偏好选择最适合执行的信号"""
|
||||
if not signals:
|
||||
return None
|
||||
|
||||
lane_priority = self.PLATFORM_SIGNAL_PRIORITY.get(platform_name, ['short_term', 'medium_term'])
|
||||
by_lane: Dict[str, List[Dict[str, Any]]] = {}
|
||||
for signal in signals:
|
||||
lane = signal.get('timeframe') or signal.get('type') or 'unknown'
|
||||
by_lane.setdefault(lane, []).append(signal)
|
||||
|
||||
for lane in lane_priority:
|
||||
candidates = by_lane.get(lane, [])
|
||||
if candidates:
|
||||
return sorted(candidates, key=lambda item: item.get('confidence', 0), reverse=True)[0]
|
||||
|
||||
return sorted(signals, key=lambda item: item.get('confidence', 0), reverse=True)[0]
|
||||
|
||||
def _build_execution_signal(self, symbol: str, signal: Dict[str, Any], current_price: float) -> Dict[str, Any]:
|
||||
"""构建传给执行规则层的标准信号格式"""
|
||||
signal_type = signal.get('timeframe') or signal.get('type') or 'medium_term'
|
||||
position_size = signal.get('position_size') or self.SIGNAL_POSITION_SIZE_DEFAULTS.get(signal_type, 'light')
|
||||
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'action': signal.get('action'),
|
||||
'confidence': signal.get('confidence', 50),
|
||||
'grade': signal.get('grade', 'C'),
|
||||
'entry_type': signal.get('entry_type', 'market'),
|
||||
'entry_price': signal.get('entry_price', current_price),
|
||||
'stop_loss': signal.get('stop_loss'),
|
||||
'take_profit': signal.get('take_profit'),
|
||||
'reasoning': signal.get('reasoning', ''),
|
||||
'timeframe': signal_type,
|
||||
'type': signal_type,
|
||||
'position_size': position_size,
|
||||
}
|
||||
|
||||
def _get_signal_for_decision(self, market_signal: Dict[str, Any], decision: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""优先返回与当前执行决策匹配的信号,用于通知和展示"""
|
||||
if not market_signal:
|
||||
return {}
|
||||
|
||||
target_timeframe = decision.get('timeframe') or decision.get('type')
|
||||
target_action = decision.get('signal_action') or decision.get('action')
|
||||
signals = market_signal.get('signals', [])
|
||||
|
||||
if target_timeframe or target_action:
|
||||
matched = [
|
||||
signal for signal in signals
|
||||
if (not target_timeframe or (signal.get('timeframe') or signal.get('type')) == target_timeframe)
|
||||
and (not target_action or signal.get('action') == target_action)
|
||||
]
|
||||
if matched:
|
||||
return sorted(matched, key=lambda item: item.get('confidence', 0), reverse=True)[0]
|
||||
|
||||
return self._get_best_signal_from_market(market_signal)
|
||||
|
||||
async def _send_market_signal_notification(self, market_signal: Dict[str, Any],
|
||||
current_price: float):
|
||||
"""发送市场信号通知(第一阶段)- 调用前已确保有有效信号"""
|
||||
@ -1444,9 +1517,12 @@ class CryptoAgent:
|
||||
title = f"[决策] {account_type} {symbol} 交易决策: {decision_text}"
|
||||
|
||||
# 获取最佳信号用于显示
|
||||
best_signal = self._get_best_signal_from_market(market_signal)
|
||||
best_signal = self._get_signal_for_decision(market_signal, decision)
|
||||
signal_confidence = best_signal.get('confidence', 0) if best_signal else 0
|
||||
signal_action = best_signal.get('action', '') if best_signal else ''
|
||||
signal_timeframe = best_signal.get('timeframe', best_signal.get('type', 'unknown')) if best_signal else 'unknown'
|
||||
timeframe_map = {'short_term': '短线', 'medium_term': '趋势', 'long_term': '长线'}
|
||||
timeframe_text = timeframe_map.get(signal_timeframe, signal_timeframe)
|
||||
|
||||
# 方向图标
|
||||
if 'buy' in signal_action.lower() or 'long' in signal_action.lower():
|
||||
@ -1461,7 +1537,7 @@ class CryptoAgent:
|
||||
|
||||
# 构建内容
|
||||
content_parts = [
|
||||
f"{action_icon} **市场信号**: {action_text} | 信心度: {signal_confidence}%",
|
||||
f"{action_icon} **市场信号**: {action_text} | {timeframe_text} | 信心度: {signal_confidence}%",
|
||||
f"",
|
||||
f"🎯 **交易决策**: {decision_text}",
|
||||
f"",
|
||||
@ -1568,7 +1644,7 @@ class CryptoAgent:
|
||||
# confidence 优先从决策本身读取,否则从市场信号的最佳信号读取
|
||||
confidence = decision.get('confidence')
|
||||
if confidence is None:
|
||||
_best = self._get_best_signal_from_market(market_signal)
|
||||
_best = self._get_signal_for_decision(market_signal, decision)
|
||||
confidence = _best.get('confidence', 0) if _best else 0
|
||||
|
||||
# 决策类型映射
|
||||
@ -1595,8 +1671,11 @@ class CryptoAgent:
|
||||
action_text = action
|
||||
|
||||
# 从市场信号中获取入场方式(需要在构建标题之前)
|
||||
best_signal = self._get_best_signal_from_market(market_signal)
|
||||
best_signal = self._get_signal_for_decision(market_signal, decision)
|
||||
entry_type = best_signal.get('entry_type', 'market') if best_signal else 'market'
|
||||
signal_timeframe = best_signal.get('timeframe', best_signal.get('type', 'unknown')) if best_signal else 'unknown'
|
||||
timeframe_map = {'short_term': '短线', 'medium_term': '趋势', 'long_term': '长线'}
|
||||
timeframe_text = timeframe_map.get(signal_timeframe, signal_timeframe)
|
||||
|
||||
# 对 Hyperliquid 限价单:用实际订单状态决定显示
|
||||
# resting=真的在挂单中, filled=已立即成交, None=非HL或市价单
|
||||
@ -1661,6 +1740,7 @@ class CryptoAgent:
|
||||
|
||||
content_parts = [
|
||||
f"{action_icon} **操作**: {decision_text} ({action_text})",
|
||||
f"🧭 **信号类型**: {timeframe_text}",
|
||||
f"{entry_type_icon} **入场方式**: {entry_type_text}",
|
||||
f"{position_display.replace(' ', ': **')} | 📈 信心度: **{confidence}%**",
|
||||
f"",
|
||||
@ -1711,10 +1791,15 @@ class CryptoAgent:
|
||||
|
||||
action_text = "做多" if 'buy' in action.lower() else ("做空" if 'sell' in action.lower() else action)
|
||||
decision_text = {'OPEN': '开仓', 'ADD': '加仓'}.get(decision_type, decision_type)
|
||||
best_signal = self._get_signal_for_decision(market_signal, decision)
|
||||
signal_timeframe = best_signal.get('timeframe', best_signal.get('type', 'unknown')) if best_signal else 'unknown'
|
||||
timeframe_map = {'short_term': '短线', 'medium_term': '趋势', 'long_term': '长线'}
|
||||
timeframe_text = timeframe_map.get(signal_timeframe, signal_timeframe)
|
||||
|
||||
title = f"{title_prefix}⚠️ {symbol} {decision_text}未执行"
|
||||
content = "\n".join([
|
||||
f"🔴 **决策**: {decision_text}({action_text})",
|
||||
f"🧭 **信号类型**: {timeframe_text}",
|
||||
f"❌ **未执行原因**: {reason}",
|
||||
f"🕐 **时间**: {datetime.now().strftime('%H:%M:%S')}",
|
||||
])
|
||||
@ -1753,10 +1838,10 @@ class CryptoAgent:
|
||||
# 转换决策的 action 为 paper_trading 期望的格式
|
||||
trading_action = self._convert_trading_action(action)
|
||||
|
||||
# 从市场信号中获取入场方式和入场价格
|
||||
best_signal = self._get_best_signal_from_market(market_signal)
|
||||
entry_type = best_signal.get('entry_type', 'market') if best_signal else 'market'
|
||||
entry_price = best_signal.get('entry_price', current_price) if best_signal else current_price
|
||||
# 兼容旧入口,但信号选择仍按当前决策对应的 lane 匹配
|
||||
matched_signal = self._get_signal_for_decision(market_signal, decision)
|
||||
entry_type = matched_signal.get('entry_type', 'market') if matched_signal else 'market'
|
||||
entry_price = matched_signal.get('entry_price', current_price) if matched_signal else current_price
|
||||
|
||||
logger.info(f" 入场方式: {entry_type} | 入场价格: ${entry_price:,.2f}")
|
||||
|
||||
@ -2267,6 +2352,8 @@ class CryptoAgent:
|
||||
Returns:
|
||||
(margin, reason) - 保证金金额和原因
|
||||
"""
|
||||
signal_type = signal.get('timeframe') or signal.get('type') or 'medium_term'
|
||||
|
||||
# 基础保证金比例(超激进配置 - 最大化资金利用率)
|
||||
confidence = signal.get('confidence', 50)
|
||||
if confidence >= 90:
|
||||
@ -2279,6 +2366,8 @@ class CryptoAgent:
|
||||
base_margin_pct = 0.08 # C级: 8% (轻仓试探)
|
||||
grade = 'C'
|
||||
|
||||
base_margin_pct *= self.SIGNAL_MARGIN_MULTIPLIERS.get(signal_type, 1.0)
|
||||
|
||||
# 可用保证金
|
||||
available = account.get('available', account.get('available_balance', 0))
|
||||
balance = account.get('current_balance', 0)
|
||||
@ -2321,7 +2410,7 @@ class CryptoAgent:
|
||||
if margin > available:
|
||||
margin = available * 0.95 # 留 5% 余量
|
||||
|
||||
return round(margin, 2), f"信号{grade}级({confidence}%) → {base_margin_pct*100}%保证金"
|
||||
return round(margin, 2), f"{signal_type} 信号{grade}级({confidence}%) → {base_margin_pct*100:.1f}%保证金"
|
||||
|
||||
def _handle_same_direction(self, signal: Dict[str, Any],
|
||||
positions: List[Dict],
|
||||
@ -2437,6 +2526,7 @@ class CryptoAgent:
|
||||
return "OPEN", "无反向订单,正常开仓"
|
||||
|
||||
def _check_risk_control(self, signal: Dict[str, Any],
|
||||
platform_name: str,
|
||||
account: Dict[str, Any],
|
||||
positions: List[Dict],
|
||||
pending_orders: List[Dict]) -> tuple:
|
||||
@ -2457,7 +2547,7 @@ class CryptoAgent:
|
||||
# 2. 可用余额检查
|
||||
available = account.get('available', account.get('available_balance', 0))
|
||||
symbol = signal.get('symbol', '').replace('USDT', '').upper()
|
||||
rules = self.PLATFORM_RULES.get('Bitget', {}) # 使用 Bitget 规则检查最小保证金
|
||||
rules = self.PLATFORM_RULES.get(platform_name, {})
|
||||
min_margin = rules.get('min_margin', {}).get(symbol, 10)
|
||||
|
||||
if available < min_margin:
|
||||
@ -2473,6 +2563,9 @@ class CryptoAgent:
|
||||
sl = signal.get('stop_loss')
|
||||
tp = signal.get('take_profit')
|
||||
|
||||
signal_type = signal.get('timeframe') or signal.get('type') or 'medium_term'
|
||||
min_rr = 1.5 if signal_type == 'short_term' else 1.8 if signal_type == 'medium_term' else 1.2
|
||||
|
||||
if entry > 0 and sl and tp:
|
||||
try:
|
||||
sl = float(sl)
|
||||
@ -2487,8 +2580,8 @@ class CryptoAgent:
|
||||
|
||||
if risk > 0:
|
||||
risk_reward_ratio = reward / risk
|
||||
if risk_reward_ratio < 1.2:
|
||||
return False, f"盈亏比 {risk_reward_ratio:.2f} < 1.2,不执行"
|
||||
if risk_reward_ratio < min_rr:
|
||||
return False, f"{signal_type} 盈亏比 {risk_reward_ratio:.2f} < {min_rr:.1f},不执行"
|
||||
except:
|
||||
pass # 价格解析失败,跳过检查
|
||||
|
||||
@ -2512,17 +2605,26 @@ class CryptoAgent:
|
||||
Returns:
|
||||
执行决策字典
|
||||
"""
|
||||
if not signal:
|
||||
return {
|
||||
"decision": "HOLD",
|
||||
"action": "IGNORE",
|
||||
"reason": "无适配信号",
|
||||
"reasoning": "无适配信号"
|
||||
}
|
||||
|
||||
logger.info(f"\n🎯 [{platform_name}] 处理交易信号: {signal.get('action')} {signal.get('symbol')}")
|
||||
|
||||
# 1. 风控检查
|
||||
passed, reason = self._check_risk_control(signal, account, positions, pending_orders)
|
||||
passed, reason = self._check_risk_control(signal, platform_name, account, positions, pending_orders)
|
||||
if not passed:
|
||||
logger.info(f" ❌ 风控未通过: {reason}")
|
||||
return {
|
||||
"decision": "HOLD",
|
||||
"action": "IGNORE",
|
||||
"reason": reason,
|
||||
"reasoning": reason
|
||||
"reasoning": reason,
|
||||
**signal
|
||||
}
|
||||
|
||||
# 2. 处理同向订单
|
||||
@ -2534,7 +2636,8 @@ class CryptoAgent:
|
||||
"decision": "HOLD",
|
||||
"action": same_action,
|
||||
"reason": same_reason,
|
||||
"reasoning": same_reason
|
||||
"reasoning": same_reason,
|
||||
**signal
|
||||
}
|
||||
|
||||
# 3. 处理反向订单
|
||||
@ -2604,6 +2707,9 @@ class CryptoAgent:
|
||||
"take_profit": signal.get('take_profit'),
|
||||
"confidence": signal.get('confidence', 0),
|
||||
"grade": signal.get('grade', 'C'),
|
||||
"timeframe": signal.get('timeframe'),
|
||||
"type": signal.get('type'),
|
||||
"position_size": signal.get('position_size', 'light'),
|
||||
}
|
||||
|
||||
async def _execute_bitget_decisions(self, decision: Dict[str, Any],
|
||||
@ -3312,32 +3418,6 @@ class CryptoAgent:
|
||||
# 发生错误时返回 0,不开仓
|
||||
return 0
|
||||
|
||||
def _convert_to_paper_signal(self, symbol: str, signal: Dict[str, Any],
|
||||
current_price: float) -> Dict[str, Any]:
|
||||
"""转换 LLM 信号格式为模拟交易格式"""
|
||||
signal_type = signal.get('type', 'medium_term')
|
||||
type_map = {'short_term': 'short_term', 'medium_term': 'swing', 'long_term': 'swing'}
|
||||
|
||||
# 获取入场类型和入场价
|
||||
entry_type = signal.get('entry_type', 'market')
|
||||
entry_price = signal.get('entry_price', current_price)
|
||||
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'action': signal.get('action', 'hold'),
|
||||
'entry_type': entry_type, # market 或 limit
|
||||
'entry_price': entry_price, # 入场价(挂单价格)
|
||||
'price': current_price, # 当前价格
|
||||
'stop_loss': signal.get('stop_loss', 0),
|
||||
'take_profit': signal.get('take_profit', 0),
|
||||
'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()
|
||||
}
|
||||
|
||||
def _calculate_price_change(self, h1_data: pd.DataFrame) -> str:
|
||||
"""计算24小时价格变化"""
|
||||
if len(h1_data) < 24:
|
||||
@ -3351,7 +3431,7 @@ class CryptoAgent:
|
||||
|
||||
def _validate_data(self, data: Dict[str, pd.DataFrame]) -> bool:
|
||||
"""验证数据完整性"""
|
||||
required_intervals = ['1m', '5m', '15m', '30m', '1h']
|
||||
required_intervals = ['1m', '5m', '15m', '30m', '1h', '4h']
|
||||
for interval in required_intervals:
|
||||
if interval not in data or data[interval].empty:
|
||||
return False
|
||||
@ -3420,34 +3500,6 @@ class CryptoAgent:
|
||||
except Exception as e:
|
||||
logger.error(f"持仓回顾失败: {e}", exc_info=True)
|
||||
|
||||
def _convert_to_real_signal(self, symbol: str, signal: Dict[str, Any],
|
||||
current_price: float) -> Dict[str, Any]:
|
||||
"""转换 LLM 信号格式为实盘交易格式"""
|
||||
signal_type = signal.get('type', 'medium_term')
|
||||
type_map = {'short_term': 'short_term', 'medium_term': 'swing', 'long_term': 'swing'}
|
||||
|
||||
# 获取入场类型和入场价
|
||||
entry_type = signal.get('entry_type', 'market')
|
||||
entry_price = signal.get('entry_price', current_price)
|
||||
|
||||
# 映射 action: buy -> long, sell -> short
|
||||
action = signal.get('action', 'hold')
|
||||
side_map = {'buy': 'long', 'sell': 'short'}
|
||||
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'side': side_map.get(action, 'long'),
|
||||
'entry_type': entry_type,
|
||||
'entry_price': entry_price,
|
||||
'stop_loss': signal.get('stop_loss', 0),
|
||||
'take_profit': signal.get('take_profit', 0),
|
||||
'confidence': signal.get('confidence', 0),
|
||||
'grade': signal.get('grade', 'D'),
|
||||
'signal_type': type_map.get(signal_type, 'swing'),
|
||||
'position_size': signal.get('position_size', 'light'), # LLM 建议的仓位大小
|
||||
'trend': signal.get('trend')
|
||||
}
|
||||
|
||||
async def _notify_position_adjustment(
|
||||
self,
|
||||
symbol: str,
|
||||
@ -3512,14 +3564,16 @@ class CryptoAgent:
|
||||
symbol = market_signal.get('symbol')
|
||||
account_type = "📊"
|
||||
|
||||
# 获取最佳信号
|
||||
best_signal = self._get_best_signal_from_market(market_signal)
|
||||
if not best_signal:
|
||||
signal = self._get_signal_for_decision(market_signal, decision)
|
||||
if not signal:
|
||||
return
|
||||
|
||||
confidence = best_signal.get('confidence', 0)
|
||||
entry_type = best_signal.get('entry_type', 'market')
|
||||
entry_price = best_signal.get('entry_price', current_price)
|
||||
confidence = signal.get('confidence', 0)
|
||||
entry_type = signal.get('entry_type', 'market')
|
||||
entry_price = signal.get('entry_price', current_price)
|
||||
signal_timeframe = signal.get('timeframe', signal.get('type', 'unknown'))
|
||||
timeframe_map = {'short_term': '短线', 'medium_term': '趋势', 'long_term': '长线'}
|
||||
timeframe_text = timeframe_map.get(signal_timeframe, signal_timeframe)
|
||||
|
||||
# 决策信息
|
||||
decision_type = decision.get('decision', 'HOLD')
|
||||
@ -3534,7 +3588,7 @@ class CryptoAgent:
|
||||
final_reason = "未知原因"
|
||||
|
||||
# 方向图标
|
||||
action = best_signal.get('action', 'wait')
|
||||
action = signal.get('action', 'wait')
|
||||
if action == 'buy':
|
||||
action_icon = '🟢'
|
||||
action_text = '做多'
|
||||
@ -3550,7 +3604,7 @@ class CryptoAgent:
|
||||
|
||||
# 构建内容
|
||||
content_parts = [
|
||||
f"{action_icon} **信号**: {action_text} | 📈 信心度: **{confidence}%**",
|
||||
f"{action_icon} **信号**: {action_text} | {timeframe_text} | 📈 信心度: **{confidence}%**",
|
||||
f"",
|
||||
f"**入场方式**: {entry_type}",
|
||||
f"**建议入场价**: ${entry_price:,.2f}" if isinstance(entry_price, (int, float)) else f"**建议入场价**: {entry_price}",
|
||||
@ -3584,11 +3638,9 @@ class CryptoAgent:
|
||||
return {'error': '数据不完整'}
|
||||
|
||||
# 使用新架构:市场分析 + 交易决策
|
||||
previous_signal = self.last_signals.get(symbol)
|
||||
market_signal = await self.market_analyzer.analyze(
|
||||
symbol, data,
|
||||
symbols=self.symbols,
|
||||
previous_signal=previous_signal
|
||||
symbols=self.symbols
|
||||
)
|
||||
|
||||
positions, account = self._get_trading_state()
|
||||
|
||||
@ -46,6 +46,9 @@ class PaperTradingExecutor(BaseExecutor):
|
||||
signal_grade = 'D'
|
||||
|
||||
# 构建信号字典
|
||||
raw_signal_type = decision.get('timeframe') or decision.get('type', 'swing')
|
||||
signal_type = 'short_term' if raw_signal_type == 'short_term' else 'swing'
|
||||
|
||||
signal = {
|
||||
'symbol': symbol,
|
||||
'action': action,
|
||||
@ -57,6 +60,9 @@ class PaperTradingExecutor(BaseExecutor):
|
||||
'confidence': confidence,
|
||||
'quantity': adjusted_margin,
|
||||
'position_size': decision.get('position_size', 'light'),
|
||||
'signal_type': signal_type,
|
||||
'type': raw_signal_type,
|
||||
'reason': decision.get('reasoning', decision.get('reason', '')),
|
||||
}
|
||||
|
||||
# 执行下单(统一调用方式)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,7 @@ class BitgetService:
|
||||
'15m': '15m',
|
||||
'30m': '30m',
|
||||
'1h': '1H', # Bitget 大写
|
||||
'4h': '4H', # Bitget 大写
|
||||
}
|
||||
|
||||
# Bitget API 基础 URL
|
||||
@ -104,7 +105,7 @@ class BitgetService:
|
||||
category: 产品类型,默认 USDT-FUTURES
|
||||
|
||||
Returns:
|
||||
包含 1m, 5m, 15m, 30m, 1h 数据的字典
|
||||
包含 1m, 5m, 15m, 30m, 1h, 4h 数据的字典
|
||||
"""
|
||||
# 不同周期使用不同的数据量,平衡分析深度和性能
|
||||
# 1m: 200根 = 3.3小时(超短线精确入场)
|
||||
@ -112,16 +113,18 @@ class BitgetService:
|
||||
# 15m: 200根 = 2.1天(短线分析)
|
||||
# 30m: 200根 = 4.2天(日内趋势)
|
||||
# 1h: 300根 = 12.5天(日内主趋势)
|
||||
# 4h: 180根 = 30天(趋势判断)
|
||||
limits = {
|
||||
'1m': 200,
|
||||
'5m': 200,
|
||||
'15m': 200,
|
||||
'30m': 200,
|
||||
'1h': 300
|
||||
'1h': 300,
|
||||
'4h': 180
|
||||
}
|
||||
|
||||
data = {}
|
||||
for interval in ['1m', '5m', '15m', '30m', '1h']:
|
||||
for interval in ['1m', '5m', '15m', '30m', '1h', '4h']:
|
||||
df = self.get_klines(symbol, interval, limit=limits.get(interval, 100),
|
||||
category=category)
|
||||
if not df.empty:
|
||||
@ -183,6 +186,7 @@ class BitgetService:
|
||||
'15m': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50},
|
||||
'30m': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50},
|
||||
'1h': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50},
|
||||
'4h': {'ma_short': 5, 'ma_mid': 10, 'ma_long': 20, 'ma_extra': 50},
|
||||
}
|
||||
|
||||
config = ma_config.get(interval, ma_config['1h'])
|
||||
|
||||
@ -358,7 +358,7 @@
|
||||
挂单中 ({{ pendingOrders.length }})
|
||||
</button>
|
||||
<button class="tab" :class="{ active: currentTab === 'history' }" @click="switchTab('history')">
|
||||
历史订单 ({{ orderHistory.length }})
|
||||
历史订单
|
||||
</button>
|
||||
<button class="tab" :class="{ active: currentTab === 'stats' }" @click="switchTab('stats')">
|
||||
详细统计
|
||||
|
||||
Loading…
Reference in New Issue
Block a user