This commit is contained in:
aaron 2026-02-23 11:35:28 +08:00
parent 24a77796d1
commit 5a270542ea
5 changed files with 663 additions and 82 deletions

View File

@ -340,6 +340,7 @@ async def get_service_status():
if service:
account = service.get_account_status()
status["account"] = account
status["auto_trading_enabled"] = service.get_auto_trading_status()
return {
"success": True,
@ -348,3 +349,60 @@ async def get_service_status():
except Exception as e:
logger.error(f"获取实盘交易服务状态失败: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/auto-trading")
async def set_auto_trading(enabled: bool = Query(..., description="是否启用自动交易")):
"""
设置实盘自动交易开关
Args:
enabled: true=启用自动交易false=禁用自动交易
"""
try:
service = get_real_trading_service()
if not service:
raise HTTPException(status_code=404, detail="实盘交易服务未初始化,请检查 API 配置")
success = service.set_auto_trading(enabled)
if success:
status_text = "启用" if enabled else "禁用"
return {
"success": True,
"message": f"实盘自动交易已{status_text}",
"auto_trading_enabled": enabled
}
else:
raise HTTPException(status_code=500, detail="设置自动交易失败")
except HTTPException:
raise
except Exception as e:
logger.error(f"设置自动交易失败: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/auto-trading")
async def get_auto_trading_status():
"""获取实盘自动交易状态"""
try:
service = get_real_trading_service()
if not service:
return {
"success": False,
"message": "实盘交易服务未初始化",
"auto_trading_enabled": False
}
enabled = service.get_auto_trading_status()
return {
"success": True,
"auto_trading_enabled": enabled
}
except Exception as e:
logger.error(f"获取自动交易状态失败: {e}")
raise HTTPException(status_code=500, detail=str(e))

View File

@ -43,12 +43,16 @@ class CryptoAgent:
self.llm_analyzer = LLMSignalAnalyzer()
self.signal_db = get_signal_db_service() # 信号数据库服务
# 模拟交易服务
self.paper_trading_enabled = self.settings.paper_trading_enabled
if self.paper_trading_enabled:
# 模拟交易服务(始终启用)
self.paper_trading = get_paper_trading_service()
else:
self.paper_trading = None
# 实盘交易服务(如果配置了 API
self.real_trading = None
try:
from app.services.real_trading_service import get_real_trading_service
self.real_trading = get_real_trading_service()
except Exception as e:
logger.warning(f"实盘交易服务初始化失败: {e}")
# 状态管理
self.last_signals: Dict[str, Dict[str, Any]] = {}
@ -75,8 +79,13 @@ class CryptoAgent:
})
logger.info(f"加密货币智能体初始化完成LLM 驱动),监控交易对: {self.symbols}")
if self.paper_trading_enabled:
logger.info(f"模拟交易已启用")
logger.info(f"模拟交易: 始终启用")
if self.real_trading:
auto_status = "启用" if self.real_trading.get_auto_trading_status() else "禁用"
logger.info(f"实盘交易: 已配置 (自动交易: {auto_status})")
else:
logger.info(f"实盘交易: 未配置")
def _on_price_update(self, symbol: str, price: float):
"""处理实时价格更新(用于模拟交易)"""
@ -495,8 +504,8 @@ class CryptoAgent:
self.last_signals[symbol] = best_signal
self.signal_cooldown[symbol] = datetime.now()
# 5. 创建模拟订单
if self.paper_trading_enabled and self.paper_trading:
# 5. 创建模拟订单(始终执行)
if self.paper_trading:
grade = best_signal.get('grade', 'D')
position_size = best_signal.get('position_size', 'light')
if grade != 'D':
@ -513,6 +522,24 @@ class CryptoAgent:
order = result.get('order')
if order:
logger.info(f" 📝 已创建模拟订单: {order.order_id} | 仓位: {position_size}")
# 6. 创建实盘订单(如果启用了自动交易)
if self.real_trading and self.real_trading.get_auto_trading_status():
grade = best_signal.get('grade', 'D')
position_size = best_signal.get('position_size', 'light')
if grade != 'D':
# 转换信号格式以兼容 real_trading
real_signal = self._convert_to_real_signal(symbol, best_signal, current_price)
result = self.real_trading.create_order_from_signal(real_signal, current_price)
if result.get('success'):
logger.info(f" 💰 已创建实盘订单: {result.get('order_id')} | 仓位: {position_size} | 数量: ${result.get('quantity', 0):.2f}")
# 发送实盘订单成交通知
await self._notify_real_order_created(symbol, best_signal, result)
elif not result.get('skipped'):
# 只有非跳过的情况才记录错误
logger.warning(f" ⚠️ 实盘订单创建失败: {result.get('message')}")
else:
if best_signal:
logger.info(f"\n⏸️ 信号冷却中或置信度不足,不发送通知")
@ -698,6 +725,62 @@ 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_real_order_created(
self,
symbol: str,
signal: Dict[str, Any],
result: Dict[str, Any]
):
"""发送实盘订单创建通知"""
side = signal.get('action', 'buy')
side_text = "做多" if side == 'buy' else "做空"
grade = signal.get('grade', 'N/A')
position_size = result.get('position_size', 'light')
quantity = result.get('quantity', 0)
message = f"""💰 实盘订单已创建
交易对: {symbol}
方向: {side_text}
等级: {grade}
仓位: {position_size}
数量: ${quantity:.2f}
订单 ID: {result.get('order_id', '')[:12]}...
真实资金交易中"""
await self.feishu.send_text(message)
await self.telegram.send_message(message)
logger.info(f"已发送实盘订单创建通知: {result.get('order_id')}")
async def _notify_position_adjustment(
self,
symbol: str,

View File

@ -0,0 +1,198 @@
"""
仓位管理服务 - 供模拟盘和实盘共用
基于 LLM 建议动态计算仓位大小
"""
from typing import Dict, Any, Optional, Tuple
from abc import ABC, abstractmethod
from app.utils.logger import logger
class PositionCalculator(ABC):
"""仓位计算器抽象基类"""
@abstractmethod
def get_account_status(self) -> Dict[str, Any]:
"""获取账户状态"""
pass
@abstractmethod
def get_max_leverage(self) -> int:
"""获取最大杠杆"""
pass
class PositionManager:
"""
仓位管理器
根据 LLM 建议的仓位大小heavy/medium/light动态计算实际保证金和持仓价值
模拟盘和实盘共用同一套仓位管理逻辑
"""
def __init__(self, calculator: PositionCalculator):
"""
初始化仓位管理器
Args:
calculator: 仓位计算器模拟盘或实盘
"""
self.calculator = calculator
def calculate_position(
self,
position_size: str,
symbol: str,
custom_ratios: Optional[Dict[str, float]] = None
) -> Tuple[float, float]:
"""
根据 LLM 建议计算仓位
Args:
position_size: LLM 建议的仓位大小 ('heavy', 'medium', 'light')
symbol: 交易对
custom_ratios: 自定义仓位比例可选
Returns:
(margin, position_value) 元组
"""
account = self.calculator.get_account_status()
max_leverage = self.calculator.get_max_leverage()
balance = account['current_balance']
current_position_value = account.get('total_position_value', 0)
# 计算可用保证金空间
max_position_value = balance * max_leverage
available_position_value = max_position_value - current_position_value
if available_position_value <= 0:
logger.warning(f"已达最大杠杆限制,无法开仓")
return 0, 0
# 使用自定义比例或默认比例
ratios = custom_ratios or {
'heavy': 0.30,
'medium': 0.15,
'light': 0.05
}
size_ratio = ratios.get(position_size, 0.05)
# 计算目标持仓价值
target_position_value = available_position_value * size_ratio
# 设置最小和最大限制
min_position_value = 1000 # 最小持仓价值
max_single_position = balance * 5 # 单笔最大不超过 5x 杠杆
position_value = max(min_position_value, min(target_position_value, max_single_position))
position_value = min(position_value, available_position_value)
position_value = round(position_value, 2)
# 计算对应的保证金
margin = round(position_value / max_leverage, 2)
logger.info(f"动态仓位计算: {position_size} | 可用空间: ${available_position_value:,.0f} | "
f"目标仓位: ${position_value:,.0f} | 保证金: ${margin:,.0f}")
return margin, position_value
class PaperPositionCalculator(PositionCalculator):
"""模拟盘仓位计算器"""
def __init__(self, account_status_getter, max_leverage: int = 20):
"""
初始化模拟盘计算器
Args:
account_status_getter: 获取账户状态的函数
max_leverage: 最大杠杆倍数
"""
self.account_status_getter = account_status_getter
self._max_leverage = max_leverage
def get_account_status(self) -> Dict[str, Any]:
return self.account_status_getter()
def get_max_leverage(self) -> int:
return self._max_leverage
class RealPositionCalculator(PositionCalculator):
"""实盘仓位计算器"""
def __init__(self, balance: float, used_margin: float, total_position_value: float, max_leverage: int = 10):
"""
初始化实盘计算器
Args:
balance: 账户余额
used_margin: 已用保证金
total_position_value: 总持仓价值
max_leverage: 最大杠杆倍数
"""
self._account_status = {
'current_balance': balance,
'used_margin': used_margin,
'total_position_value': total_position_value
}
self._max_leverage = max_leverage
def get_account_status(self) -> Dict[str, Any]:
return self._account_status
def get_max_leverage(self) -> int:
return self._max_leverage
def calculate_paper_position(
account_status_getter,
position_size: str,
symbol: str,
max_leverage: int = 20
) -> Tuple[float, float]:
"""
计算模拟盘仓位快捷方法
Args:
account_status_getter: 获取账户状态的函数
position_size: LLM 建议的仓位大小
symbol: 交易对
max_leverage: 最大杠杆倍数
Returns:
(margin, position_value) 元组
"""
calculator = PaperPositionCalculator(account_status_getter, max_leverage)
manager = PositionManager(calculator)
return manager.calculate_position(position_size, symbol)
def calculate_real_position(
balance: float,
used_margin: float,
total_position_value: float,
position_size: str,
symbol: str,
max_leverage: int = 10,
custom_ratios: Optional[Dict[str, float]] = None
) -> Tuple[float, float]:
"""
计算实盘仓位快捷方法
Args:
balance: 账户余额
used_margin: 已用保证金
total_position_value: 总持仓价值
position_size: LLM 建议的仓位大小
symbol: 交易对
max_leverage: 最大杠杆倍数
custom_ratios: 自定义仓位比例实盘可用更保守的配置
Returns:
(margin, position_value) 元组
"""
calculator = RealPositionCalculator(balance, used_margin, total_position_value, max_leverage)
manager = PositionManager(calculator)
return manager.calculate_position(position_size, symbol, custom_ratios)

View File

@ -2,6 +2,7 @@
实盘交易服务 - Bitget 合约交易
提供与模拟交易服务类似的接口但执行的是真实交易
集成 LLM 仓位管理决策
"""
import uuid
from datetime import datetime, timedelta
@ -10,6 +11,7 @@ from typing import Dict, Any, List, Optional
from app.models.real_trading import RealOrder
from app.models.paper_trading import OrderStatus, OrderSide, SignalGrade, EntryType
from app.services.db_service import db_service
from app.services.position_manager import calculate_real_position
from app.config import get_settings
from app.utils.logger import logger
@ -29,6 +31,9 @@ class RealTradingService:
self.risk_per_trade = self.settings.real_trading_risk_per_trade
self.max_orders = self.settings.real_trading_max_orders
# 自动交易开关(从数据库加载)
self.auto_trading_enabled = self._load_auto_trading_status()
# 获取交易 API (使用 CCXT SDK 版本)
from app.services.bitget_trading_api_sdk import get_bitget_trading_api
self.trading_api = get_bitget_trading_api()
@ -44,14 +49,81 @@ class RealTradingService:
self._load_active_orders()
logger.info(f"实盘交易服务初始化完成(最大单笔: ${self.max_single_position}"
f"杠杆: {self.default_leverage}x最大持仓: {self.max_orders}")
f"杠杆: {self.default_leverage}x最大持仓: {self.max_orders}"
f"自动交易: {'启用' if self.auto_trading_enabled else '禁用'}")
def _ensure_table_exists(self):
"""确保数据表已创建"""
from app.models.real_trading import RealOrder
from app.models.database import Base
from sqlalchemy import text
Base.metadata.create_all(bind=db_service.engine)
# 创建自动交易开关表
db = db_service.get_session()
try:
db.execute(text("""
CREATE TABLE IF NOT EXISTS real_trading_settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
"""))
db.commit()
# 初始化自动交易开关
db.execute(text("""
INSERT OR IGNORE INTO real_trading_settings (key, value)
VALUES ('auto_trading_enabled', '0')
"""))
db.commit()
except Exception as e:
logger.warning(f"创建设置表失败: {e}")
db.rollback()
finally:
db.close()
def _load_auto_trading_status(self) -> bool:
"""从数据库加载自动交易开关状态"""
db = db_service.get_session()
try:
from sqlalchemy import text
result = db.execute(text("SELECT value FROM real_trading_settings WHERE key = 'auto_trading_enabled'")).fetchone()
if result:
return result[0] == '1'
return False
except Exception as e:
logger.warning(f"加载自动交易状态失败: {e}")
return False
finally:
db.close()
def set_auto_trading(self, enabled: bool) -> bool:
"""设置自动交易开关"""
db = db_service.get_session()
try:
from sqlalchemy import text
db.execute(text("""
UPDATE real_trading_settings
SET value = :value, updated_at = CURRENT_TIMESTAMP
WHERE key = 'auto_trading_enabled'
"""), {'value': '1' if enabled else '0'})
db.commit()
self.auto_trading_enabled = enabled
logger.info(f"实盘自动交易已{'启用' if enabled else '禁用'}")
return True
except Exception as e:
logger.error(f"设置自动交易失败: {e}")
db.rollback()
return False
finally:
db.close()
def get_auto_trading_status(self) -> bool:
"""获取自动交易状态"""
return self.auto_trading_enabled
def _load_active_orders(self):
"""从数据库加载活跃订单"""
db = db_service.get_session()
@ -70,15 +142,33 @@ class RealTradingService:
def create_order_from_signal(self, signal: Dict[str, Any], current_price: float = None) -> Dict[str, Any]:
"""
从信号创建实盘订单
从信号创建实盘订单集成 LLM 仓位管理
Args:
signal: LLM 分析信号
- symbol: 交易对
- side: 'long' or 'short'
- entry_type: 'market' or 'limit'
- entry_price: 入场价
- stop_loss: 止损价
- take_profit: 止盈价
- grade: 信号等级
- confidence: 置信度
- position_size: LLM 建议的仓位大小 ('heavy', 'medium', 'light')
current_price: 当前价格
Returns:
创建结果
"""
# 检查自动交易开关
if not self.auto_trading_enabled:
logger.info(f"实盘自动交易已禁用,跳过信号执行")
return {
'success': False,
'message': '实盘自动交易已禁用',
'skipped': True
}
if not self.trading_api:
return {
'success': False,
@ -109,6 +199,7 @@ class RealTradingService:
take_profit = signal.get('take_profit')
grade = signal.get('grade', 'D')
confidence = signal.get('confidence', 0)
position_size = signal.get('position_size', 'light') # LLM 建议的仓位大小
# 验证必需参数
if not all([symbol, side, stop_loss, take_profit]):
@ -141,12 +232,52 @@ class RealTradingService:
'message': f'已达最大持仓数 {self.max_orders}'
}
# 风险检查 - 检查账户余额
balance_info = self._check_balance_risk(symbol, entry_price, stop_loss)
if not balance_info['can_trade']:
# 获取账户状态
account = self.get_account_status()
balance = account['current_balance']
available = account['available']
used_margin = account['used_margin']
total_position_value = account['total_position_value']
if available < 10:
return {
'success': False,
'message': balance_info['reason']
'message': f'可用余额不足 (${available:.2f})'
}
# === 使用 LLM 建议的仓位大小计算仓位 ===
# 实盘使用更保守的比例配置
custom_ratios = {
'heavy': 0.15, # 激进方案heavy 15%(模拟盘是 30%
'medium': 0.08, # 激进方案medium 8%(模拟盘是 15%
'light': 0.03 # 激进方案light 3%(模拟盘是 5%
}
# 计算仓位(使用统一的仓位管理器)
margin, position_value = calculate_real_position(
balance=balance,
used_margin=used_margin,
total_position_value=total_position_value,
position_size=position_size,
symbol=symbol,
max_leverage=self.default_leverage,
custom_ratios=custom_ratios
)
if margin <= 0 or position_value <= 0:
return {
'success': False,
'message': '无法开仓:仓位计算失败或已达杠杆限制'
}
quantity = position_value # 订单数量(以 USDT 计价)
# 最小仓位限制100 美金测试,最小 5 USDT
min_quantity = 5
if quantity < min_quantity:
return {
'success': False,
'message': f'计算仓位 ${quantity:.2f} 小于最小值 ${min_quantity}'
}
# 创建订单对象
@ -158,14 +289,14 @@ class RealTradingService:
entry_price=entry_price,
stop_loss=stop_loss,
take_profit=take_profit,
quantity=balance_info['quantity'],
quantity=quantity,
leverage=self.default_leverage,
signal_grade=SignalGrade[grade.upper()] if grade.upper() in ['A', 'B', 'C', 'D'] else SignalGrade.D,
signal_type=signal.get('signal_type', 'swing'),
confidence=confidence,
trend=signal.get('trend'),
entry_type=EntryType.MARKET if entry_type == 'market' else EntryType.LIMIT,
status=OrderStatus.PENDING
status=OrderStatus.OPEN if entry_type == 'market' else OrderStatus.PENDING
)
db.add(order)
@ -189,11 +320,15 @@ class RealTradingService:
self.active_orders[order_id] = order
result['success'] = True
result['message'] = '实盘订单创建成功'
result['message'] = f'实盘订单创建成功 (仓位: {position_size})'
result['order_id'] = order_id
result['exchange_order_id'] = exchange_result.get('order_id')
result['position_size'] = position_size
result['quantity'] = quantity
logger.info(f"✅ 实盘订单创建成功: {symbol} {side} ${entry_price} -> {exchange_result.get('order_id')}")
logger.info(f"✅ 实盘订单创建成功: {symbol} {side} ${entry_price} | "
f"仓位: {position_size} | 数量: ${quantity:.2f} | "
f"-> {exchange_result.get('order_id')}")
else:
# 下单失败,删除记录
db.delete(order)
@ -213,59 +348,6 @@ class RealTradingService:
return result
def _check_balance_risk(self, symbol: str, entry_price: float, stop_loss: float) -> Dict:
"""
检查余额和风险
Returns:
{'can_trade': bool, 'reason': str, 'quantity': float}
"""
try:
# 获取账户余额
balance_info = self.trading_api.get_balance()
usdt_balance = balance_info.get('USDT', {})
available = float(usdt_balance.get('available', 0))
if available < 10:
return {
'can_trade': False,
'reason': f'可用余额不足 (${available:.2f})',
'quantity': 0
}
# 计算风险
risk_amount = entry_price - stop_loss
risk_percent = (risk_amount / entry_price) * 100
# 根据风险计算仓位
max_quantity = (available * self.risk_per_trade) / (risk_percent / 100)
# 限制单笔最大仓位
max_quantity = min(max_quantity, self.max_single_position)
# 最小仓位限制
min_quantity = 10
if max_quantity < min_quantity:
return {
'can_trade': False,
'reason': f'风险过高,计算仓位 ${max_quantity:.2f} 小于最小值 ${min_quantity}',
'quantity': 0
}
return {
'can_trade': True,
'reason': '',
'quantity': round(max_quantity, 2)
}
except Exception as e:
logger.error(f"检查余额风险失败: {e}")
return {
'can_trade': False,
'reason': f'风险检查失败: {str(e)}',
'quantity': 0
}
def _place_bitget_order(self, order: RealOrder, current_price: float) -> Dict:
"""
调用 Bitget API 下单 (使用 CCXT SDK)
@ -401,6 +483,43 @@ class RealTradingService:
'available': 0
}
def get_position_info(self) -> Dict[str, Any]:
"""
获取当前持仓信息 LLM 分析使用
Returns:
持仓信息字典
"""
account = self.get_account_status()
active_orders = self.get_active_orders()
# 计算当前杠杆
balance = account['current_balance']
total_position_value = account['total_position_value']
current_leverage = total_position_value / balance if balance > 0 else 0
# 格式化持仓列表
positions = []
for order in active_orders:
positions.append({
'symbol': order.get('symbol'),
'side': order.get('side'),
'status': order.get('status'),
'entry_price': order.get('filled_price') or order.get('entry_price'),
'quantity': order.get('quantity'),
'pnl_percent': order.get('pnl_percent', 0)
})
return {
'account_balance': balance,
'total_position_value': total_position_value,
'current_leverage': current_leverage,
'max_leverage': self.default_leverage,
'active_order_count': len(active_orders),
'max_orders': self.max_orders,
'positions': positions
}
# 全局实例
_real_trading_service: Optional[RealTradingService] = None
@ -410,8 +529,11 @@ def get_real_trading_service() -> Optional[RealTradingService]:
"""
获取实盘交易服务实例单例
注意不再检查 REAL_TRADING_ENABLED 配置
只要 API 配置了就初始化服务自动交易可以单独控制
Returns:
RealTradingService 实例或 None如果未配置或未启用
RealTradingService 实例或 None如果未配置 API
"""
global _real_trading_service
@ -420,11 +542,7 @@ def get_real_trading_service() -> Optional[RealTradingService]:
settings = get_settings()
# 检查是否启用实盘交易
if not settings.real_trading_enabled:
return None
# 检查是否配置了 API Key
# 检查是否配置了 API Key不再检查 REAL_TRADING_ENABLED
if not settings.bitget_api_key or not settings.bitget_api_secret:
logger.warning("Bitget API Key 未配置,实盘交易功能不可用")
return None

View File

@ -362,6 +362,81 @@
color: var(--text-secondary);
margin-top: 4px;
}
.switch-container {
display: flex;
align-items: center;
gap: 12px;
}
.switch-label {
font-size: 14px;
color: var(--text-primary);
}
.switch {
position: relative;
display: inline-block;
width: 50px;
height: 26px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #444;
transition: .4s;
border-radius: 26px;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: var(--accent);
}
input:checked + .slider:before {
transform: translateX(24px);
}
.switch-disabled {
opacity: 0.5;
cursor: not-allowed;
}
.status-text {
font-size: 12px;
color: var(--text-secondary);
min-width: 60px;
}
.status-text.enabled {
color: #00ff41;
}
.status-text.disabled {
color: #ff4444;
}
</style>
</head>
<body>
@ -374,11 +449,28 @@
实盘交易
<span class="real-badge">LIVE</span>
</div>
<div style="display: flex; align-items: center; gap: 16px;">
<!-- 自动交易开关 -->
<div class="switch-container" v-if="apiConfigured">
<span class="switch-label">自动交易</span>
<label class="switch" :class="{ 'switch-disabled': !serviceEnabled }">
<input type="checkbox"
v-model="autoTradingEnabled"
@change="toggleAutoTrading"
:disabled="!serviceEnabled || switching">
<span class="slider"></span>
</label>
<span class="status-text"
:class="{ enabled: autoTradingEnabled, disabled: !autoTradingEnabled }">
{{ autoTradingEnabled ? '已启用' : '已禁用' }}
</span>
</div>
<button class="refresh-btn" @click="refreshData">
刷新
</button>
</div>
</div>
</div>
<!-- 警告横幅 - 只在API未配置时显示 -->
<div class="warning-banner" v-if="!apiConfigured">
@ -560,6 +652,8 @@
serviceEnabled: false,
apiConfigured: false,
useTestnet: true,
autoTradingEnabled: false,
switching: false,
account: {
current_balance: 0,
available: 0,
@ -622,6 +716,8 @@
this.serviceEnabled = status.enabled;
this.apiConfigured = status.api_configured;
this.useTestnet = status.use_testnet;
// 获取自动交易状态
this.autoTradingEnabled = status.auto_trading_enabled || false;
if (status.account) {
this.account = status.account;
}
@ -631,6 +727,34 @@
}
},
async toggleAutoTrading() {
if (this.switching) return;
this.switching = true;
try {
const response = await axios.post('/api/real-trading/auto-trading', null, {
params: { enabled: this.autoTradingEnabled }
});
if (response.data.success) {
// 刷新状态
await this.fetchServiceStatus();
alert(response.data.message);
} else {
// 恢复原状态
this.autoTradingEnabled = !this.autoTradingEnabled;
alert('设置失败: ' + (response.data.message || '未知错误'));
}
} catch (error) {
console.error('设置自动交易失败:', error);
// 恢复原状态
this.autoTradingEnabled = !this.autoTradingEnabled;
alert('设置失败: ' + (error.response?.data?.detail || error.message));
} finally {
this.switching = false;
}
},
async fetchAccountStatus() {
try {
const response = await axios.get('/api/real-trading/account');