This commit is contained in:
aaron 2026-03-30 00:00:39 +08:00
parent 44fc91dfc2
commit f31322a2a5
5 changed files with 922 additions and 497 deletions

View File

@ -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()

View File

@ -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

View File

@ -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'])

View File

@ -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')">
详细统计