增加 bitget 实盘的数据基础设施
This commit is contained in:
parent
46d0a12c02
commit
7376fefc10
21
.env.example
21
.env.example
@ -150,6 +150,27 @@ PAPER_TRADING_POSITION_A=1000
|
||||
PAPER_TRADING_POSITION_B=500
|
||||
PAPER_TRADING_POSITION_C=200
|
||||
|
||||
# ============================================================================
|
||||
# 实盘交易配置 - Bitget
|
||||
# ============================================================================
|
||||
# Bitget API 配置(必需)
|
||||
BITGET_API_KEY=your_bitget_api_key_here
|
||||
BITGET_API_SECRET=your_bitget_api_secret_here
|
||||
BITGET_PASSPHRASE=your_bitget_passphrase_here
|
||||
|
||||
# 是否使用测试网(强烈建议先在测试网测试!)
|
||||
BITGET_USE_TESTNET=true
|
||||
|
||||
# 实盘交易总开关(false 时仅模拟交易生效)
|
||||
REAL_TRADING_ENABLED=false
|
||||
|
||||
# 实盘交易风险控制
|
||||
REAL_TRADING_MAX_SINGLE_POSITION=1000 # 单笔最大持仓金额 (USDT)
|
||||
REAL_TRADING_MAX_TOTAL_RATIO=0.5 # 最大总仓位比例(账户的50%)
|
||||
REAL_TRADING_DEFAULT_LEVERAGE=10 # 实盘默认杠杆(低于模拟)
|
||||
REAL_TRADING_RISK_PER_TRADE=0.02 # 每笔交易风险比例(2%)
|
||||
REAL_TRADING_MAX_ORDERS=5 # 实盘最大同时持仓数
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Agent 模型配置
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
@ -140,6 +140,23 @@ class Settings(BaseSettings):
|
||||
paper_trading_position_b: float = 500 # B级信号仓位 (USDT)
|
||||
paper_trading_position_c: float = 200 # C级信号仓位 (USDT)
|
||||
|
||||
# ========== 实盘交易配置 ==========
|
||||
# Bitget API 配置
|
||||
bitget_api_key: str = "" # Bitget API Key
|
||||
bitget_api_secret: str = "" # Bitget API Secret
|
||||
bitget_passphrase: str = "" # Bitget API Passphrase
|
||||
bitget_use_testnet: bool = True # 是否使用测试网(测试时设为 true)
|
||||
|
||||
# 实盘交易开关
|
||||
real_trading_enabled: bool = False # 实盘交易总开关(false 时仅模拟交易生效)
|
||||
|
||||
# 实盘交易风险控制
|
||||
real_trading_max_single_position: float = 1000 # 单笔最大持仓金额 (USDT)
|
||||
real_trading_max_total_ratio: float = 0.5 # 最大总仓位比例(账户的50%)
|
||||
real_trading_default_leverage: int = 10 # 实盘默认杠杆(低于模拟)
|
||||
real_trading_risk_per_trade: float = 0.02 # 每笔交易风险比例(2%)
|
||||
real_trading_max_orders: int = 5 # 实盘最大同时持仓数
|
||||
|
||||
# Agent 模型配置 (可选值: zhipu, deepseek)
|
||||
smart_agent_model: str = "deepseek" # SmartAgent 使用的模型
|
||||
crypto_agent_model: str = "deepseek" # CryptoAgent 使用的模型
|
||||
|
||||
109
backend/app/models/real_trading.py
Normal file
109
backend/app/models/real_trading.py
Normal file
@ -0,0 +1,109 @@
|
||||
"""
|
||||
实盘交易数据模型
|
||||
|
||||
与模拟交易使用相同的订单状态和方向枚举
|
||||
"""
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, Integer, String, Float, DateTime, JSON, Text, Enum as SQLEnum
|
||||
from app.models.database import Base
|
||||
from app.models.paper_trading import OrderStatus, OrderSide, SignalGrade, EntryType
|
||||
|
||||
|
||||
class RealOrder(Base):
|
||||
"""实盘交易订单表"""
|
||||
__tablename__ = "real_orders"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
|
||||
# 订单标识
|
||||
order_id = Column(String(64), unique=True, nullable=False, index=True) # 本地订单ID
|
||||
exchange_order_id = Column(String(64), nullable=True, index=True) # 交易所订单ID
|
||||
client_order_id = Column(String(64), nullable=True, index=True) # 自定义订单ID
|
||||
|
||||
# 交易对信息
|
||||
symbol = Column(String(20), nullable=False, index=True)
|
||||
side = Column(SQLEnum(OrderSide), nullable=False)
|
||||
|
||||
# 价格信息
|
||||
entry_price = Column(Float, nullable=False) # 目标入场价
|
||||
stop_loss = Column(Float, nullable=False) # 止损价
|
||||
take_profit = Column(Float, nullable=False) # 止盈价
|
||||
filled_price = Column(Float, nullable=True) # 实际成交价
|
||||
exit_price = Column(Float, nullable=True) # 出场价
|
||||
|
||||
# 仓位信息
|
||||
quantity = Column(Float, default=1000) # 仓位大小 (USDT)
|
||||
leverage = Column(Integer, default=10) # 杠杆倍数
|
||||
size = Column(Float, nullable=True) # 合约数量(张数)
|
||||
|
||||
# 信号信息
|
||||
signal_grade = Column(SQLEnum(SignalGrade), default=SignalGrade.D)
|
||||
signal_type = Column(String(20), default="swing") # swing / short_term
|
||||
confidence = Column(Float, default=0) # 置信度 (0-100)
|
||||
trend = Column(String(20), nullable=True) # 趋势方向
|
||||
entry_type = Column(SQLEnum(EntryType, values_callable=lambda x: [e.value for e in x]), default=EntryType.MARKET)
|
||||
|
||||
# 订单状态
|
||||
status = Column(SQLEnum(OrderStatus), default=OrderStatus.PENDING, index=True)
|
||||
|
||||
# 盈亏信息
|
||||
pnl_amount = Column(Float, default=0) # 盈亏金额 (USDT)
|
||||
pnl_percent = Column(Float, default=0) # 盈亏百分比
|
||||
fee_amount = Column(Float, default=0) # 手续费
|
||||
|
||||
# 风险指标
|
||||
max_drawdown = Column(Float, default=0) # 持仓期间最大回撤
|
||||
max_profit = Column(Float, default=0) # 持仓期间最大盈利
|
||||
|
||||
# 移动止损相关
|
||||
breakeven_triggered = Column(Integer, default=0) # 保本止损是否已触发
|
||||
trailing_stop_triggered = Column(Integer, default=0) # 移动止损是否已触发
|
||||
trailing_stop_base_profit = Column(Float, default=0) # 移动止损基准盈利
|
||||
|
||||
# 时间戳
|
||||
created_at = Column(DateTime, default=datetime.now, index=True)
|
||||
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
filled_at = Column(DateTime, nullable=True) # 成交时间
|
||||
closed_at = Column(DateTime, nullable=True) # 平仓时间
|
||||
|
||||
# 额外数据
|
||||
extra_data = Column(JSON, nullable=True) # 存储额外信息(metadata是保留字)
|
||||
notes = Column(Text, nullable=True) # 备注
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""转换为字典"""
|
||||
return {
|
||||
'id': self.id,
|
||||
'order_id': self.order_id,
|
||||
'exchange_order_id': self.exchange_order_id,
|
||||
'client_order_id': self.client_order_id,
|
||||
'symbol': self.symbol,
|
||||
'side': self.side.value if self.side else None,
|
||||
'entry_price': self.entry_price,
|
||||
'stop_loss': self.stop_loss,
|
||||
'take_profit': self.take_profit,
|
||||
'filled_price': self.filled_price,
|
||||
'exit_price': self.exit_price,
|
||||
'quantity': self.quantity,
|
||||
'leverage': self.leverage,
|
||||
'size': self.size,
|
||||
'signal_grade': self.signal_grade.value if self.signal_grade else None,
|
||||
'signal_type': self.signal_type,
|
||||
'confidence': self.confidence,
|
||||
'trend': self.trend,
|
||||
'entry_type': self.entry_type.value if self.entry_type else None,
|
||||
'status': self.status.value if self.status else None,
|
||||
'pnl_amount': self.pnl_amount,
|
||||
'pnl_percent': self.pnl_percent,
|
||||
'fee_amount': self.fee_amount,
|
||||
'max_drawdown': self.max_drawdown,
|
||||
'max_profit': self.max_profit,
|
||||
'breakeven_triggered': bool(self.breakeven_triggered),
|
||||
'trailing_stop_triggered': bool(self.trailing_stop_triggered),
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
|
||||
'filled_at': self.filled_at.isoformat() if self.filled_at else None,
|
||||
'closed_at': self.closed_at.isoformat() if self.closed_at else None,
|
||||
'extra_data': self.extra_data,
|
||||
'notes': self.notes
|
||||
}
|
||||
542
backend/app/services/bitget_trading_api_sdk.py
Normal file
542
backend/app/services/bitget_trading_api_sdk.py
Normal file
@ -0,0 +1,542 @@
|
||||
"""
|
||||
Bitget 实盘交易 API (基于 CCXT SDK)
|
||||
|
||||
处理 Bitget 合约交易的所有 API 调用,包括:
|
||||
- 下单(开仓/平仓)
|
||||
- 撤单
|
||||
- 查询订单
|
||||
- 查询持仓
|
||||
- 查询账户余额
|
||||
- 设置杠杆
|
||||
|
||||
使用 CCXT 统一交易所接口,提供更稳定的 API 交互。
|
||||
"""
|
||||
import ccxt
|
||||
from typing import Dict, List, Optional, Any
|
||||
from datetime import datetime
|
||||
from app.utils.logger import logger
|
||||
|
||||
|
||||
class BitgetTradingAPI:
|
||||
"""Bitget 实盘交易 API (基于 CCXT)"""
|
||||
|
||||
def __init__(self, api_key: str, api_secret: str, passphrase: str = "", use_testnet: bool = True):
|
||||
"""
|
||||
初始化 Bitget 交易 API
|
||||
|
||||
Args:
|
||||
api_key: API Key
|
||||
api_secret: API Secret
|
||||
passphrase: API Passphrase (Bitget 不需要,保留兼容性)
|
||||
use_testnet: 是否使用测试网
|
||||
"""
|
||||
self.api_key = api_key
|
||||
self.api_secret = api_secret
|
||||
self.use_testnet = use_testnet
|
||||
|
||||
# 创建 CCXT Bitget 实例
|
||||
config = {
|
||||
'apiKey': api_key,
|
||||
'secret': api_secret,
|
||||
'enableRateLimit': True, # 启用速率限制
|
||||
'options': {
|
||||
'defaultType': 'swap', # 使用永续合约 API
|
||||
}
|
||||
}
|
||||
|
||||
# CCXT Bitget 使用 password 字段存储 passphrase
|
||||
# 如果提供了 passphrase,添加到配置中
|
||||
if passphrase:
|
||||
config['password'] = passphrase
|
||||
|
||||
self.exchange = ccxt.bitget(config)
|
||||
|
||||
# 设置测试网(sandbox 模式)
|
||||
if use_testnet:
|
||||
if hasattr(self.exchange, 'set_sandbox_mode'):
|
||||
self.exchange.set_sandbox_mode(True)
|
||||
logger.info("✅ Bitget 测试网模式已启用")
|
||||
|
||||
logger.info(f"Bitget 交易 API 初始化完成 ({'测试网' if use_testnet else '生产网'})")
|
||||
|
||||
# ==================== 订单操作 ====================
|
||||
|
||||
def place_order(self, symbol: str, side: str, order_type: str,
|
||||
size: float, price: float = None, client_order_id: str = None) -> Optional[Dict]:
|
||||
"""
|
||||
下单
|
||||
|
||||
Args:
|
||||
symbol: 交易对 (如 BTC/USDT:USDT)
|
||||
side: 订单方向 (buy/sell)
|
||||
order_type: 订单类型 (limit/market)
|
||||
size: 数量(张数)
|
||||
price: 价格(限价单必需)
|
||||
client_order_id: 自定义订单ID
|
||||
|
||||
Returns:
|
||||
订单信息
|
||||
"""
|
||||
try:
|
||||
# CCXT 标准化交易对格式
|
||||
ccxt_symbol = self._standardize_symbol(symbol)
|
||||
|
||||
# 构建订单参数
|
||||
params = {}
|
||||
if client_order_id:
|
||||
params['clientOrderId'] = client_order_id
|
||||
|
||||
# 下单
|
||||
if order_type == 'market':
|
||||
order = self.exchange.create_market_order(
|
||||
symbol=ccxt_symbol,
|
||||
side=side,
|
||||
amount=size,
|
||||
params=params
|
||||
)
|
||||
else: # limit
|
||||
if not price:
|
||||
logger.error("限价单必须指定价格")
|
||||
return None
|
||||
order = self.exchange.create_limit_order(
|
||||
symbol=ccxt_symbol,
|
||||
side=side,
|
||||
amount=size,
|
||||
price=price,
|
||||
params=params
|
||||
)
|
||||
|
||||
logger.info(f"✅ 下单成功: {symbol} {side} {size}张 @ {price or '市价'}")
|
||||
logger.debug(f"订单详情: {order}")
|
||||
return order
|
||||
|
||||
except ccxt.BaseError as e:
|
||||
logger.error(f"❌ 下单失败: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 下单异常: {e}")
|
||||
return None
|
||||
|
||||
def cancel_order(self, symbol: str, order_id: str = None, client_order_id: str = None) -> bool:
|
||||
"""
|
||||
撤单
|
||||
|
||||
Args:
|
||||
symbol: 交易对
|
||||
order_id: 订单ID
|
||||
client_order_id: 自定义订单ID
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
try:
|
||||
ccxt_symbol = self._standardize_symbol(symbol)
|
||||
|
||||
if order_id:
|
||||
self.exchange.cancel_order(order_id, ccxt_symbol)
|
||||
elif client_order_id:
|
||||
self.exchange.cancel_order_by_client_order_id(client_order_id, ccxt_symbol)
|
||||
else:
|
||||
logger.error("必须提供 order_id 或 client_order_id")
|
||||
return False
|
||||
|
||||
logger.info(f"✅ 撤单成功: {symbol} order_id={order_id or client_order_id}")
|
||||
return True
|
||||
|
||||
except ccxt.BaseError as e:
|
||||
logger.error(f"❌ 撤单失败: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 撤单异常: {e}")
|
||||
return False
|
||||
|
||||
def cancel_all_orders(self, symbol: str) -> bool:
|
||||
"""
|
||||
撤销所有挂单
|
||||
|
||||
Args:
|
||||
symbol: 交易对
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
try:
|
||||
ccxt_symbol = self._standardize_symbol(symbol)
|
||||
self.exchange.cancel_all_orders(ccxt_symbol)
|
||||
|
||||
logger.info(f"✅ 撤销所有挂单成功: {symbol}")
|
||||
return True
|
||||
|
||||
except ccxt.BaseError as e:
|
||||
logger.error(f"❌ 撤销所有挂单失败: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 撤销所有挂单异常: {e}")
|
||||
return False
|
||||
|
||||
def close_position(self, symbol: str, side: str, size: float = None,
|
||||
price: float = None) -> Optional[Dict]:
|
||||
"""
|
||||
平仓
|
||||
|
||||
Args:
|
||||
symbol: 交易对
|
||||
side: 平仓方向 (buy=平空仓/sell=平多仓)
|
||||
size: 平仓数量(不传则全部平仓)
|
||||
price: 平仓价格(不传则市价)
|
||||
|
||||
Returns:
|
||||
订单信息
|
||||
"""
|
||||
try:
|
||||
ccxt_symbol = self._standardize_symbol(symbol)
|
||||
|
||||
# 获取当前持仓
|
||||
positions = self.get_position(symbol)
|
||||
if not positions:
|
||||
logger.warning(f"没有找到 {symbol} 的持仓")
|
||||
return None
|
||||
|
||||
position = positions[0]
|
||||
current_size = abs(float(position.get('contracts', 0)))
|
||||
|
||||
if current_size == 0:
|
||||
logger.warning(f"{symbol} 持仓数量为 0,无需平仓")
|
||||
return None
|
||||
|
||||
# 如果没有指定平仓数量,则全部平仓
|
||||
close_size = size if size else current_size
|
||||
|
||||
# 执行平仓
|
||||
order_type = 'limit' if price else 'market'
|
||||
order = self.place_order(
|
||||
symbol=symbol,
|
||||
side=side,
|
||||
order_type=order_type,
|
||||
size=close_size,
|
||||
price=price
|
||||
)
|
||||
|
||||
if order:
|
||||
logger.info(f"✅ 平仓成功: {symbol} {side} {close_size}张")
|
||||
return order
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 平仓异常: {e}")
|
||||
return None
|
||||
|
||||
# ==================== 查询操作 ====================
|
||||
|
||||
def get_order(self, symbol: str, order_id: str = None, client_order_id: str = None) -> Optional[Dict]:
|
||||
"""
|
||||
查询订单
|
||||
|
||||
Args:
|
||||
symbol: 交易对
|
||||
order_id: 订单ID
|
||||
client_order_id: 自定义订单ID
|
||||
|
||||
Returns:
|
||||
订单信息
|
||||
"""
|
||||
try:
|
||||
ccxt_symbol = self._standardize_symbol(symbol)
|
||||
|
||||
if order_id:
|
||||
order = self.exchange.fetch_order(order_id, ccxt_symbol)
|
||||
elif client_order_id:
|
||||
order = self.exchange.fetch_order_by_client_order_id(client_order_id, ccxt_symbol)
|
||||
else:
|
||||
logger.error("必须提供 order_id 或 client_order_id")
|
||||
return None
|
||||
|
||||
return order
|
||||
|
||||
except ccxt.BaseError as e:
|
||||
logger.error(f"❌ 查询订单失败: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 查询订单异常: {e}")
|
||||
return None
|
||||
|
||||
def get_open_orders(self, symbol: str = None) -> List[Dict]:
|
||||
"""
|
||||
查询当前挂单
|
||||
|
||||
Args:
|
||||
symbol: 交易对(不传则查询所有)
|
||||
|
||||
Returns:
|
||||
挂单列表
|
||||
"""
|
||||
try:
|
||||
ccxt_symbol = self._standardize_symbol(symbol) if symbol else None
|
||||
orders = self.exchange.fetch_open_orders(ccxt_symbol)
|
||||
|
||||
logger.debug(f"查询到 {len(orders)} 个挂单")
|
||||
return orders
|
||||
|
||||
except ccxt.BaseError as e:
|
||||
logger.error(f"❌ 查询挂单失败: {e}")
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 查询挂单异常: {e}")
|
||||
return []
|
||||
|
||||
def get_history_orders(self, symbol: str, limit: int = 100) -> List[Dict]:
|
||||
"""
|
||||
查询历史订单
|
||||
|
||||
Args:
|
||||
symbol: 交易对
|
||||
limit: 返回数量
|
||||
|
||||
Returns:
|
||||
历史订单列表
|
||||
"""
|
||||
try:
|
||||
ccxt_symbol = self._standardize_symbol(symbol)
|
||||
orders = self.exchange.fetch_my_trades(ccxt_symbol, limit=limit)
|
||||
|
||||
logger.debug(f"查询到 {len(orders)} 条历史订单")
|
||||
return orders
|
||||
|
||||
except ccxt.BaseError as e:
|
||||
logger.error(f"❌ 查询历史订单失败: {e}")
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 查询历史订单异常: {e}")
|
||||
return []
|
||||
|
||||
# ==================== 持仓操作 ====================
|
||||
|
||||
def get_position(self, symbol: str = None) -> List[Dict]:
|
||||
"""
|
||||
查询持仓
|
||||
|
||||
Args:
|
||||
symbol: 交易对(不传则查询所有)
|
||||
|
||||
Returns:
|
||||
持仓列表
|
||||
"""
|
||||
try:
|
||||
# 获取所有持仓
|
||||
positions = self.exchange.fetch_positions()
|
||||
|
||||
# 筛选非零持仓
|
||||
active_positions = []
|
||||
for pos in positions:
|
||||
size = float(pos.get('contracts', 0))
|
||||
if size != 0:
|
||||
# 如果指定了交易对,进行过滤
|
||||
if symbol:
|
||||
ccxt_symbol = self._standardize_symbol(symbol)
|
||||
if pos['symbol'] == ccxt_symbol:
|
||||
active_positions.append(pos)
|
||||
else:
|
||||
active_positions.append(pos)
|
||||
|
||||
logger.debug(f"查询到 {len(active_positions)} 个持仓")
|
||||
return active_positions
|
||||
|
||||
except ccxt.BaseError as e:
|
||||
logger.error(f"❌ 查询持仓失败: {e}")
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 查询持仓异常: {e}")
|
||||
return []
|
||||
|
||||
def get_single_position(self, symbol: str) -> Optional[Dict]:
|
||||
"""
|
||||
查询单个交易对的持仓
|
||||
|
||||
Args:
|
||||
symbol: 交易对
|
||||
|
||||
Returns:
|
||||
持仓信息
|
||||
"""
|
||||
positions = self.get_position(symbol)
|
||||
if positions:
|
||||
return positions[0]
|
||||
return None
|
||||
|
||||
# ==================== 账户操作 ====================
|
||||
|
||||
def get_balance(self) -> Dict:
|
||||
"""
|
||||
查询账户余额
|
||||
|
||||
Returns:
|
||||
余额信息 {USDT: {available: "...", frozen: "...", locked: "..."}}
|
||||
"""
|
||||
try:
|
||||
balance = self.exchange.fetch_balance()
|
||||
|
||||
# 转换为统一格式
|
||||
result = {}
|
||||
for currency, info in balance.get('total', {}).items():
|
||||
if info and info > 0: # 只返回有余额的币种
|
||||
result[currency] = {
|
||||
'available': str(balance.get('free', {}).get(currency, 0)),
|
||||
'frozen': str(balance.get('used', {}).get(currency, 0)),
|
||||
'locked': '0'
|
||||
}
|
||||
|
||||
logger.debug(f"账户余额: {result}")
|
||||
return result
|
||||
|
||||
except ccxt.BaseError as e:
|
||||
logger.error(f"❌ 查询余额失败: {e}")
|
||||
return {}
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 查询余额异常: {e}")
|
||||
return {}
|
||||
|
||||
def get_account_info(self) -> Dict:
|
||||
"""
|
||||
查询账户信息
|
||||
|
||||
Returns:
|
||||
账户信息
|
||||
"""
|
||||
try:
|
||||
balance = self.exchange.fetch_balance()
|
||||
return balance
|
||||
except ccxt.BaseError as e:
|
||||
logger.error(f"❌ 查询账户信息失败: {e}")
|
||||
return {}
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 查询账户信息异常: {e}")
|
||||
return {}
|
||||
|
||||
def set_leverage(self, symbol: str, leverage: int, hold_side: str = "long") -> bool:
|
||||
"""
|
||||
设置杠杆倍数
|
||||
|
||||
Args:
|
||||
symbol: 交易对
|
||||
leverage: 杠杆倍数 (1-125)
|
||||
hold_side: 持仓方向 (long/short) - CCXT 通常设置为 both
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
try:
|
||||
ccxt_symbol = self._standardize_symbol(symbol)
|
||||
|
||||
# CCXT 设置杠杆
|
||||
self.exchange.set_leverage(leverage, ccxt_symbol)
|
||||
|
||||
logger.info(f"✅ 设置杠杆成功: {symbol} {leverage}x")
|
||||
return True
|
||||
|
||||
except ccxt.BaseError as e:
|
||||
logger.error(f"❌ 设置杠杆失败: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 设置杠杆异常: {e}")
|
||||
return False
|
||||
|
||||
# ==================== 辅助方法 ====================
|
||||
|
||||
def _standardize_symbol(self, symbol: str) -> str:
|
||||
"""
|
||||
标准化交易对格式为 CCXT 格式
|
||||
|
||||
Args:
|
||||
symbol: 原始交易对 (如 BTCUSDT)
|
||||
|
||||
Returns:
|
||||
CCXT 标准格式 (如 BTC/USDT:USDT)
|
||||
"""
|
||||
# 如果已经是 CCXT 格式,直接返回
|
||||
if '/' in symbol:
|
||||
return symbol
|
||||
|
||||
# 简单的转换逻辑(可以根据实际情况扩展)
|
||||
# 例如:BTCUSDT -> BTC/USDT:USDT
|
||||
if symbol.endswith('USDT'):
|
||||
base = symbol[:-4] # 去掉 USDT
|
||||
return f"{base}/USDT:USDT"
|
||||
|
||||
# 默认返回原值
|
||||
return symbol
|
||||
|
||||
def test_connection(self) -> bool:
|
||||
"""
|
||||
测试 API 连接
|
||||
|
||||
Returns:
|
||||
是否连接成功
|
||||
"""
|
||||
try:
|
||||
balance = self.get_balance()
|
||||
if balance is not None:
|
||||
usdt_balance = balance.get('USDT', {}).get('available', 'N/A')
|
||||
logger.info(f"✅ API 连接成功,USDT 余额: {usdt_balance}")
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"❌ API 连接失败: {e}")
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
"""关闭连接"""
|
||||
if self.exchange:
|
||||
# CCXT exchange 对象不需要显式关闭
|
||||
# 但可以清理 WebSocket 连接(如果有的话)
|
||||
try:
|
||||
if hasattr(self.exchange, 'close'):
|
||||
self.exchange.close()
|
||||
except Exception as e:
|
||||
logger.debug(f"关闭连接时出错(可忽略): {e}")
|
||||
logger.info("Bitget API 连接已关闭")
|
||||
|
||||
|
||||
# 全局实例(延迟初始化)
|
||||
_trading_api: Optional[BitgetTradingAPI] = None
|
||||
|
||||
|
||||
def get_bitget_trading_api() -> Optional[BitgetTradingAPI]:
|
||||
"""
|
||||
获取 Bitget 交易 API 实例(单例)
|
||||
|
||||
Returns:
|
||||
BitgetTradingAPI 实例或 None(如果未配置)
|
||||
"""
|
||||
global _trading_api
|
||||
|
||||
if _trading_api:
|
||||
return _trading_api
|
||||
|
||||
from app.config import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
# 检查是否配置了 API Key
|
||||
if not settings.bitget_api_key or not settings.bitget_api_secret:
|
||||
logger.warning("Bitget API Key 未配置,实盘交易功能不可用")
|
||||
return None
|
||||
|
||||
# 创建实例
|
||||
_trading_api = BitgetTradingAPI(
|
||||
api_key=settings.bitget_api_key,
|
||||
api_secret=settings.bitget_api_secret,
|
||||
passphrase=settings.bitget_passphrase,
|
||||
use_testnet=settings.bitget_use_testnet
|
||||
)
|
||||
|
||||
return _trading_api
|
||||
|
||||
|
||||
def reset_bitget_trading_api():
|
||||
"""重置全局实例(用于测试或配置更新)"""
|
||||
global _trading_api
|
||||
if _trading_api:
|
||||
_trading_api.close()
|
||||
_trading_api = None
|
||||
logger.info("Bitget API 实例已重置")
|
||||
433
backend/app/services/real_trading_service.py
Normal file
433
backend/app/services/real_trading_service.py
Normal file
@ -0,0 +1,433 @@
|
||||
"""
|
||||
实盘交易服务 - Bitget 合约交易
|
||||
|
||||
提供与模拟交易服务类似的接口,但执行的是真实交易
|
||||
"""
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
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.config import get_settings
|
||||
from app.utils.logger import logger
|
||||
|
||||
|
||||
class RealTradingService:
|
||||
"""实盘交易服务"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化实盘交易服务"""
|
||||
self.settings = get_settings()
|
||||
self.active_orders: Dict[str, RealOrder] = {} # 内存缓存活跃订单
|
||||
|
||||
# 实盘交易配置
|
||||
self.max_single_position = self.settings.real_trading_max_single_position
|
||||
self.max_total_ratio = self.settings.real_trading_max_total_ratio
|
||||
self.default_leverage = self.settings.real_trading_default_leverage
|
||||
self.risk_per_trade = self.settings.real_trading_risk_per_trade
|
||||
self.max_orders = self.settings.real_trading_max_orders
|
||||
|
||||
# 获取交易 API (使用 CCXT SDK 版本)
|
||||
from app.services.bitget_trading_api_sdk import get_bitget_trading_api
|
||||
self.trading_api = get_bitget_trading_api()
|
||||
|
||||
if not self.trading_api:
|
||||
logger.error("Bitget 交易 API 未初始化,实盘交易功能不可用")
|
||||
return
|
||||
|
||||
# 确保表已创建
|
||||
self._ensure_table_exists()
|
||||
|
||||
# 加载活跃订单
|
||||
self._load_active_orders()
|
||||
|
||||
logger.info(f"实盘交易服务初始化完成(最大单笔: ${self.max_single_position},"
|
||||
f"杠杆: {self.default_leverage}x,最大持仓: {self.max_orders})")
|
||||
|
||||
def _ensure_table_exists(self):
|
||||
"""确保数据表已创建"""
|
||||
from app.models.real_trading import RealOrder
|
||||
from app.models.database import Base
|
||||
Base.metadata.create_all(bind=db_service.engine)
|
||||
|
||||
def _load_active_orders(self):
|
||||
"""从数据库加载活跃订单"""
|
||||
db = db_service.get_session()
|
||||
try:
|
||||
orders = db.query(RealOrder).filter(
|
||||
RealOrder.status.in_([OrderStatus.PENDING, OrderStatus.OPEN])
|
||||
).all()
|
||||
|
||||
for order in orders:
|
||||
self.active_orders[order.order_id] = order
|
||||
|
||||
logger.info(f"实盘交易: 加载了 {len(orders)} 个活跃订单")
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def create_order_from_signal(self, signal: Dict[str, Any], current_price: float = None) -> Dict[str, Any]:
|
||||
"""
|
||||
从信号创建实盘订单
|
||||
|
||||
Args:
|
||||
signal: LLM 分析信号
|
||||
current_price: 当前价格
|
||||
|
||||
Returns:
|
||||
创建结果
|
||||
"""
|
||||
if not self.trading_api:
|
||||
return {
|
||||
'success': False,
|
||||
'message': '交易 API 未初始化'
|
||||
}
|
||||
|
||||
db = db_service.get_session()
|
||||
result = {
|
||||
'success': False,
|
||||
'message': '',
|
||||
'order_id': None
|
||||
}
|
||||
|
||||
try:
|
||||
# 检查实盘交易是否启用
|
||||
if not self.settings.real_trading_enabled:
|
||||
return {
|
||||
'success': False,
|
||||
'message': '实盘交易未启用,请检查配置 REAL_TRADING_ENABLED=true'
|
||||
}
|
||||
|
||||
# 获取信号信息
|
||||
symbol = signal.get('symbol')
|
||||
side = signal.get('side') # 'long' or 'short'
|
||||
entry_type = signal.get('entry_type', 'market') # 'market' or 'limit'
|
||||
entry_price = signal.get('entry_price')
|
||||
stop_loss = signal.get('stop_loss')
|
||||
take_profit = signal.get('take_profit')
|
||||
grade = signal.get('grade', 'D')
|
||||
confidence = signal.get('confidence', 0)
|
||||
|
||||
# 验证必需参数
|
||||
if not all([symbol, side, stop_loss, take_profit]):
|
||||
return {
|
||||
'success': False,
|
||||
'message': '信号缺少必需参数'
|
||||
}
|
||||
|
||||
# 获取当前价格
|
||||
if not current_price:
|
||||
from app.services.bitget_service import bitget_service
|
||||
current_price = bitget_service.get_current_price(symbol)
|
||||
|
||||
if not current_price:
|
||||
return {
|
||||
'success': False,
|
||||
'message': '无法获取当前价格'
|
||||
}
|
||||
|
||||
# 设置入场价
|
||||
if entry_type == 'market':
|
||||
entry_price = current_price
|
||||
elif not entry_price:
|
||||
entry_price = current_price
|
||||
|
||||
# 风险检查 - 检查订单数量
|
||||
if len(self.active_orders) >= self.max_orders:
|
||||
return {
|
||||
'success': False,
|
||||
'message': f'已达最大持仓数 {self.max_orders}'
|
||||
}
|
||||
|
||||
# 风险检查 - 检查账户余额
|
||||
balance_info = self._check_balance_risk(symbol, entry_price, stop_loss)
|
||||
if not balance_info['can_trade']:
|
||||
return {
|
||||
'success': False,
|
||||
'message': balance_info['reason']
|
||||
}
|
||||
|
||||
# 创建订单对象
|
||||
order_id = str(uuid.uuid4())
|
||||
order = RealOrder(
|
||||
order_id=order_id,
|
||||
symbol=symbol,
|
||||
side=OrderSide.LONG if side == 'long' else OrderSide.SHORT,
|
||||
entry_price=entry_price,
|
||||
stop_loss=stop_loss,
|
||||
take_profit=take_profit,
|
||||
quantity=balance_info['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
|
||||
)
|
||||
|
||||
db.add(order)
|
||||
db.commit()
|
||||
db.refresh(order)
|
||||
|
||||
# 调用 Bitget API 下单
|
||||
exchange_result = self._place_bitget_order(order, current_price)
|
||||
|
||||
if exchange_result['success']:
|
||||
# 更新订单状态
|
||||
order.exchange_order_id = exchange_result.get('order_id')
|
||||
order.client_order_id = exchange_result.get('client_order_id')
|
||||
order.filled_price = exchange_result.get('filled_price', entry_price)
|
||||
order.filled_at = datetime.now()
|
||||
order.status = OrderStatus.OPEN
|
||||
|
||||
db.commit()
|
||||
|
||||
# 添加到内存缓存
|
||||
self.active_orders[order_id] = order
|
||||
|
||||
result['success'] = True
|
||||
result['message'] = '实盘订单创建成功'
|
||||
result['order_id'] = order_id
|
||||
result['exchange_order_id'] = exchange_result.get('order_id')
|
||||
|
||||
logger.info(f"✅ 实盘订单创建成功: {symbol} {side} ${entry_price} -> {exchange_result.get('order_id')}")
|
||||
else:
|
||||
# 下单失败,删除记录
|
||||
db.delete(order)
|
||||
db.commit()
|
||||
|
||||
result['message'] = f"下单失败: {exchange_result.get('message', '未知错误')}"
|
||||
|
||||
logger.error(f"❌ 实盘订单下单失败: {exchange_result.get('message')}")
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
result['message'] = f"创建订单失败: {str(e)}"
|
||||
logger.error(f"创建实盘订单失败: {e}")
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
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)
|
||||
|
||||
Returns:
|
||||
{'success': bool, 'order_id': str, 'client_order_id': str, 'filled_price': float, 'message': str}
|
||||
"""
|
||||
try:
|
||||
# CCXT 使用标准的 buy/sell 方向
|
||||
# 对于合约:buy = 开多/平空,sell = 开空/平多
|
||||
# 这里我们简化为:long = buy, short = sell
|
||||
side_map = {
|
||||
OrderSide.LONG: 'buy',
|
||||
OrderSide.SHORT: 'sell'
|
||||
}
|
||||
|
||||
# 映射订单类型
|
||||
order_type = 'market' if order.entry_type == EntryType.MARKET else 'limit'
|
||||
|
||||
# 计算合约数量(张数)
|
||||
# Bitget U本位合约:1张 = 1 USDT(大多数情况)
|
||||
size = order.quantity # 简化处理,实际应该根据合约规格计算
|
||||
|
||||
# 生成自定义订单ID
|
||||
client_order_id = f"real_{order.order_id[:8]}"
|
||||
|
||||
# 调用 API 下单
|
||||
result = self.trading_api.place_order(
|
||||
symbol=order.symbol,
|
||||
side=side_map[order.side],
|
||||
order_type=order_type,
|
||||
size=size,
|
||||
price=order.entry_price if order_type == 'limit' else None,
|
||||
client_order_id=client_order_id
|
||||
)
|
||||
|
||||
if result:
|
||||
# CCXT 返回的订单对象格式
|
||||
# result['id'] 是交易所订单ID
|
||||
# result['price'] 是委托价格
|
||||
# result['average'] 是成交均价(如果已成交)
|
||||
order_id = result.get('id')
|
||||
filled_price = float(result.get('average', 0)) or current_price
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'order_id': order_id,
|
||||
'client_order_id': client_order_id,
|
||||
'filled_price': filled_price
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'success': False,
|
||||
'message': 'API 调用失败'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Bitget 下单失败: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'message': str(e)
|
||||
}
|
||||
|
||||
def get_active_orders(self) -> List[Dict]:
|
||||
"""获取活跃订单列表"""
|
||||
return [order.to_dict() for order in self.active_orders.values()]
|
||||
|
||||
def get_order(self, order_id: str) -> Optional[Dict]:
|
||||
"""获取指定订单"""
|
||||
order = self.active_orders.get(order_id)
|
||||
if order:
|
||||
return order.to_dict()
|
||||
return None
|
||||
|
||||
def sync_positions_from_exchange(self) -> List[Dict]:
|
||||
"""
|
||||
从交易所同步持仓状态
|
||||
|
||||
Returns:
|
||||
同步后的持仓列表
|
||||
"""
|
||||
if not self.trading_api:
|
||||
return []
|
||||
|
||||
try:
|
||||
# 获取交易所实际持仓
|
||||
positions = self.trading_api.get_position()
|
||||
|
||||
logger.info(f"从交易所同步了 {len(positions)} 个持仓")
|
||||
return positions
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"同步持仓失败: {e}")
|
||||
return []
|
||||
|
||||
def get_account_status(self) -> Dict:
|
||||
"""获取账户状态"""
|
||||
if not self.trading_api:
|
||||
return {
|
||||
'current_balance': 0,
|
||||
'used_margin': 0,
|
||||
'total_position_value': 0,
|
||||
'available': 0
|
||||
}
|
||||
|
||||
try:
|
||||
balance_info = self.trading_api.get_balance()
|
||||
usdt_info = balance_info.get('USDT', {})
|
||||
|
||||
available = float(usdt_info.get('available', 0))
|
||||
frozen = float(usdt_info.get('frozen', 0))
|
||||
locked = float(usdt_info.get('locked', 0))
|
||||
|
||||
# 计算持仓价值
|
||||
total_position_value = 0
|
||||
for order in self.active_orders.values():
|
||||
if order.status == OrderStatus.OPEN:
|
||||
total_position_value += order.quantity
|
||||
|
||||
return {
|
||||
'current_balance': available + frozen + locked,
|
||||
'available': available,
|
||||
'used_margin': frozen + locked,
|
||||
'total_position_value': total_position_value
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取账户状态失败: {e}")
|
||||
return {
|
||||
'current_balance': 0,
|
||||
'used_margin': 0,
|
||||
'total_position_value': 0,
|
||||
'available': 0
|
||||
}
|
||||
|
||||
|
||||
# 全局实例
|
||||
_real_trading_service: Optional[RealTradingService] = None
|
||||
|
||||
|
||||
def get_real_trading_service() -> Optional[RealTradingService]:
|
||||
"""
|
||||
获取实盘交易服务实例(单例)
|
||||
|
||||
Returns:
|
||||
RealTradingService 实例或 None(如果未配置或未启用)
|
||||
"""
|
||||
global _real_trading_service
|
||||
|
||||
if _real_trading_service:
|
||||
return _real_trading_service
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
# 检查是否启用实盘交易
|
||||
if not settings.real_trading_enabled:
|
||||
return None
|
||||
|
||||
# 检查是否配置了 API Key
|
||||
if not settings.bitget_api_key or not settings.bitget_api_secret:
|
||||
logger.warning("Bitget API Key 未配置,实盘交易功能不可用")
|
||||
return None
|
||||
|
||||
_real_trading_service = RealTradingService()
|
||||
return _real_trading_service
|
||||
@ -22,3 +22,4 @@ python-jose[cryptography]==3.3.0
|
||||
# 加密货币交易智能体依赖
|
||||
python-binance>=1.0.19
|
||||
httpx>=0.27.0
|
||||
ccxt>=4.0.0 # 统一交易所API接口,支持Bitget等主流交易所
|
||||
|
||||
191
docs/BITGET_SDK_MIGRATION.md
Normal file
191
docs/BITGET_SDK_MIGRATION.md
Normal file
@ -0,0 +1,191 @@
|
||||
# Bitget Python SDK 迁移指南
|
||||
|
||||
## 概述
|
||||
|
||||
已将 Bitget 交易 API 从手动实现迁移到使用 **CCXT Python SDK**。
|
||||
|
||||
## 主要变化
|
||||
|
||||
### 1. 依赖变更
|
||||
|
||||
**新增依赖:**
|
||||
```bash
|
||||
pip install ccxt>=4.0.0
|
||||
```
|
||||
|
||||
### 2. 文件结构
|
||||
|
||||
| 旧文件 | 新文件 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `bitget_trading_api.py` | `bitget_trading_api_sdk.py` | 使用 CCXT 的新实现 |
|
||||
| - | `scripts/test_bitget_sdk.py` | SDK 测试脚本 |
|
||||
|
||||
### 3. API 响应格式变化
|
||||
|
||||
#### 旧版本(直接 API)
|
||||
```python
|
||||
# 订单响应格式
|
||||
{
|
||||
'orderId': '123456789',
|
||||
'fillPrice': '50000.5',
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### 新版本(CCXT)
|
||||
```python
|
||||
# 订单响应格式
|
||||
{
|
||||
'id': '123456789',
|
||||
'price': '50000.0', # 委托价格
|
||||
'average': '50000.5', # 成交均价
|
||||
'status': 'closed', # CCXT 标准状态
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 订单方向变化
|
||||
|
||||
#### 旧版本
|
||||
```python
|
||||
side_map = {
|
||||
OrderSide.LONG: 'open_long',
|
||||
OrderSide.SHORT: 'open_short'
|
||||
}
|
||||
```
|
||||
|
||||
#### 新版本(CCXT 标准化)
|
||||
```python
|
||||
side_map = {
|
||||
OrderSide.LONG: 'buy',
|
||||
OrderSide.SHORT: 'sell'
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 交易对格式
|
||||
|
||||
#### 旧版本
|
||||
```
|
||||
BTCUSDT
|
||||
ETHUSDT
|
||||
```
|
||||
|
||||
#### 新版本(CCXT 标准化)
|
||||
```
|
||||
BTC/USDT:USDT
|
||||
ETH/USDT:USDT
|
||||
```
|
||||
|
||||
新实现会自动进行格式转换。
|
||||
|
||||
## 安装步骤
|
||||
|
||||
### 1. 安装依赖
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 2. 配置环境变量
|
||||
|
||||
在 `.env` 文件中配置:
|
||||
|
||||
```bash
|
||||
# Bitget API 配置
|
||||
BITGET_API_KEY=your_api_key_here
|
||||
BITGET_API_SECRET=your_api_secret_here
|
||||
BITGET_PASSPHRASE= # Bitget 不需要,保留兼容性
|
||||
BITGET_USE_TESTNET=true # 测试时设为 true
|
||||
|
||||
# 实盘交易开关
|
||||
REAL_TRADING_ENABLED=false # 生产环境再启用
|
||||
```
|
||||
|
||||
### 3. 测试连接
|
||||
|
||||
```bash
|
||||
cd scripts
|
||||
python test_bitget_sdk.py
|
||||
```
|
||||
|
||||
预期输出:
|
||||
```
|
||||
============================================================
|
||||
测试 1: API 连接测试
|
||||
============================================================
|
||||
✅ API 连接成功,USDT 余额: 10000.00
|
||||
|
||||
============================================================
|
||||
测试 2: 查询账户余额
|
||||
============================================================
|
||||
✅ 余额查询成功
|
||||
USDT: 可用=10000.00, 冻结=0.00
|
||||
...
|
||||
```
|
||||
|
||||
## CCXT 的优势
|
||||
|
||||
1. **统一接口** - 同样的代码可以支持多个交易所(Binance, OKX, Bybit 等)
|
||||
2. **更好的维护** - CCXT 团队持续更新,修复 API 变更
|
||||
3. **内置重试** - 自动处理网络问题和速率限制
|
||||
4. **标准化格式** - 统一的订单状态、交易对格式
|
||||
5. **异步支持** - 支持 async/await 模式
|
||||
|
||||
## API 功能对比
|
||||
|
||||
| 功能 | 旧实现 | 新实现(CCXT) |
|
||||
|------|--------|----------------|
|
||||
| 下单 | ✅ | ✅ |
|
||||
| 撤单 | ✅ | ✅ |
|
||||
| 查询订单 | ✅ | ✅ |
|
||||
| 查询挂单 | ✅ | ✅ |
|
||||
| 查询持仓 | ✅ | ✅ |
|
||||
| 查询余额 | ✅ | ✅ |
|
||||
| 设置杠杆 | ✅ | ✅ |
|
||||
| 测试网支持 | ✅ | ✅ |
|
||||
| 自动重试 | ❌ | ✅ |
|
||||
| 速率限制 | ❌ | ✅ |
|
||||
|
||||
## 兼容性说明
|
||||
|
||||
- ✅ `real_trading_service.py` 已更新使用新 SDK
|
||||
- ✅ 保持相同的公共接口
|
||||
- ✅ 数据库模型无需更改
|
||||
- ⚠️ 需要重新测试实盘交易功能
|
||||
|
||||
## 下一步
|
||||
|
||||
1. **在测试网验证** - 确保所有功能正常
|
||||
2. **小资金实盘测试** - 验证订单执行
|
||||
3. **监控日志** - 检查是否有任何错误
|
||||
4. **逐步迁移** - 确认稳定后使用新版本
|
||||
|
||||
## 回滚方案
|
||||
|
||||
如果需要回滚到旧版本:
|
||||
|
||||
1. 修改 `real_trading_service.py` 第 33 行:
|
||||
```python
|
||||
# 从
|
||||
from app.services.bitget_trading_api_sdk import get_bitget_trading_api
|
||||
|
||||
# 改回
|
||||
from app.services.bitget_trading_api import get_bitget_trading_api
|
||||
```
|
||||
|
||||
2. 重启服务
|
||||
|
||||
## 相关资源
|
||||
|
||||
- CCXT 官方文档: https://docs.ccxt.com/
|
||||
- CCXT Bitget 支持: https://docs.ccxt.com/#exchange-sources
|
||||
- Bitget 官方文档: https://www.bitget.com/api-doc
|
||||
|
||||
## 支持
|
||||
|
||||
如有问题,请检查:
|
||||
1. CCXT 版本是否 >= 4.0.0
|
||||
2. API 密钥是否正确
|
||||
3. 网络连接是否正常
|
||||
4. 测试网是否可用
|
||||
486
docs/REAL_TRADING_MIGRATION_PLAN.md
Normal file
486
docs/REAL_TRADING_MIGRATION_PLAN.md
Normal file
@ -0,0 +1,486 @@
|
||||
# Bitget 实盘交易迁移方案
|
||||
|
||||
> **目标**: 将当前的模拟交易系统切换到 Bitget 实盘合约交易
|
||||
>
|
||||
> **状态**: 方案制定中 - 未实施
|
||||
|
||||
---
|
||||
|
||||
## 一、当前系统架构分析
|
||||
|
||||
### 1.1 模拟交易系统现状
|
||||
|
||||
**核心文件**: `backend/app/services/paper_trading_service.py`
|
||||
|
||||
**主要功能**:
|
||||
- ✅ 订单创建 (`create_order_from_signal`)
|
||||
- ✅ 订单平仓 (`_close_order`, `close_order_manual`)
|
||||
- ✅ 止盈止损检查 (`check_price_triggers`)
|
||||
- ✅ 移动止损逻辑
|
||||
- ✅ 账户余额管理
|
||||
- ✅ 持仓信息查询
|
||||
|
||||
**订单状态管理**:
|
||||
- `PENDING` - 挂单中(等待入场价格)
|
||||
- `OPEN` - 已开仓
|
||||
- `CLOSED` - 已平仓
|
||||
|
||||
**配置参数** (`.env`):
|
||||
```bash
|
||||
PAPER_TRADING_ENABLED=true # 启用模拟交易
|
||||
PAPER_TRADING_INITIAL_BALANCE=10000 # 初始本金
|
||||
PAPER_TRADING_LEVERAGE=20 # 杠杆倍数
|
||||
PAPER_TRADING_MARGIN_PER_ORDER=1000 # 每单保证金
|
||||
PAPER_TRADING_MAX_ORDERS=10 # 最大订单数
|
||||
PAPER_TRADING_AUTO_CLOSE_OPPOSITE=false # 自动平反向持仓
|
||||
PAPER_TRADING_TRAILING_STOP_ENABLED=true # 移动止损
|
||||
```
|
||||
|
||||
### 1.2 数据流向
|
||||
|
||||
```
|
||||
LLM 分析信号 → PaperTradingService → 数据库记录
|
||||
↓
|
||||
本地模拟执行
|
||||
↓
|
||||
止盈止损检查(本地价格轮询)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、实盘交易架构设计
|
||||
|
||||
### 2.1 整体方案
|
||||
|
||||
**方案 A - 双模式并存** (推荐)
|
||||
- 保留模拟交易作为回测和测试
|
||||
- 新增实盘交易服务
|
||||
- 通过配置开关切换
|
||||
|
||||
**方案 B - 完全替换**
|
||||
- 直接将模拟交易改为实盘
|
||||
- 风险较高,不推荐
|
||||
|
||||
### 2.2 核心组件设计
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 交易服务层 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────┐ ┌──────────────────┐ │
|
||||
│ │ PaperTradingService │ │ RealTradingService │ │
|
||||
│ │ (模拟交易) │ │ (实盘交易) │ │
|
||||
│ └──────────────────┘ └──────────────────┘ │
|
||||
│ │ │ │
|
||||
│ └────────────┬───────────────┘ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────┐ │
|
||||
│ │ TradingInterface │ (统一接口) │
|
||||
│ └──────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────┐ │
|
||||
│ │ BitgetTradingAPI │ │
|
||||
│ │ (实盘交易API) │ │
|
||||
│ └──────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、Bitget 实盘 API 需求
|
||||
|
||||
### 3.1 需要 API 功能
|
||||
|
||||
| 功能 | Bitget API 端点 | 说明 |
|
||||
|------|----------------|------|
|
||||
| **下单** | `POST /api/mix/v1/order/placeOrder` | U本位合约下单 |
|
||||
| **撤单** | `POST /api/mix/v1/order/cancelOrder` | 撤销挂单 |
|
||||
| **平仓** | `POST /api/mix v1/order/closePosition` | 平仓持仓 |
|
||||
| **查询持仓** | `GET /api/mix/v1/position/allPosition` | 获取当前持仓 |
|
||||
| **查询订单** | `GET /api/mix/v1/order/orderInfo` | 获取订单状态 |
|
||||
| **修改订单** | `POST /api/mix/v1/order/modifyOrder` | 修改挂单价格 |
|
||||
| **账户余额** | `GET /api/mix/v1/account/account` | 获取账户余额 |
|
||||
| **设置杠杆** | `POST /api/mix/v1/account/setLeverage` | 设置杠杆倍数 |
|
||||
|
||||
### 3.2 Bitget 订单类型
|
||||
|
||||
```python
|
||||
# 订单方向
|
||||
side_map = {
|
||||
'long': 'open_long', # 开多
|
||||
'short': 'open_short', # 开空
|
||||
}
|
||||
|
||||
# 订单类型
|
||||
order_type_map = {
|
||||
'limit': 'limit', # 限价单(挂单)
|
||||
'market': 'market', # 市价单
|
||||
}
|
||||
|
||||
# 产品类型
|
||||
product_type = 'USDT-FUTURES' # U本位永续合约
|
||||
```
|
||||
|
||||
### 3.3 订单参数映射
|
||||
|
||||
| 模拟交易参数 | Bitget API 参数 | 说明 |
|
||||
|-------------|----------------|------|
|
||||
| `symbol` | `symbol` | 交易对 (如 BTCUSDT) |
|
||||
| `side` | `side` | open_long/close_long/open_short/close_short |
|
||||
| `order_type` | `orderType` | limit/market |
|
||||
| `quantity` | `size` | 数量(张数) |
|
||||
| `entry_price` | `price` | 委托价格 |
|
||||
| `stop_loss` | `stopLoss` | 止损价格 |
|
||||
| `take_profit` | `takeProfit` | 止盈价格 |
|
||||
| `leverage` | `leverage` | 杠杆倍数 |
|
||||
|
||||
---
|
||||
|
||||
## 四、实施步骤
|
||||
|
||||
### 阶段一:准备工作 (1-2天)
|
||||
|
||||
#### 1.1 Bitget API 密钥申请
|
||||
- [ ] 注册 Bitget 账户
|
||||
- [ ] 开启合约交易功能
|
||||
- [ ] 创建 API Key
|
||||
- [ ] 配置 IP 白名单
|
||||
- [ ] **选择测试网进行初期测试**
|
||||
|
||||
#### 1.2 测试网环境搭建
|
||||
- [ ] 获取 Bitget 测试网凭证
|
||||
- [ ] 配置测试网 API 端点:`https://api-testnet.bitget.com`
|
||||
- [ ] 验证 API 连接性
|
||||
|
||||
#### 1.3 风险控制参数设置
|
||||
```bash
|
||||
# .env 新增配置
|
||||
BITGET_API_KEY=your_api_key
|
||||
BITGET_API_SECRET=your_api_secret
|
||||
BITGET_PASSPHRASE=your_passphrase # 如果需要
|
||||
BITGET_USE_TESTNET=true # 初期使用测试网
|
||||
|
||||
# 实盘交易配置
|
||||
REAL_TRADING_ENABLED=false # 实盘交易总开关
|
||||
REAL_TRADING_MAX_POSITION=5000 # 单笔最大持仓 (USDT)
|
||||
REAL_TRADING_MAX_TOTAL_RATIO=0.5 # 最大总仓位比例(账户的50%)
|
||||
REAL_TRADING_DEFAULT_LEVERAGE=10 # 默认杠杆(低于模拟)
|
||||
REAL_TRADING_RISK_PER_TRADE=0.02 # 每笔交易风险(2%)
|
||||
```
|
||||
|
||||
### 阶段二:实盘交易服务开发 (2-3天)
|
||||
|
||||
#### 2.1 创建 `BitgetTradingAPI` 类
|
||||
|
||||
**文件**: `backend/app/services/bitget_trading_api.py`
|
||||
|
||||
```python
|
||||
class BitgetTradingAPI:
|
||||
"""Bitget 实盘交易 API"""
|
||||
|
||||
def __init__(self, api_key: str, api_secret: str, use_testnet: bool = True):
|
||||
self.api_key = api_key
|
||||
self.api_secret = api_secret
|
||||
self.base_url = TESTNET_URL if use_testnet else PROD_URL
|
||||
|
||||
def place_order(self, symbol: str, side: str, size: float,
|
||||
price: float = None, order_type: str = 'limit') -> dict:
|
||||
"""下单"""
|
||||
# POST /api/mix/v1/order/placeOrder
|
||||
|
||||
def cancel_order(self, symbol: str, order_id: str) -> dict:
|
||||
"""撤单"""
|
||||
# POST /api/mix/v1/order/cancelOrder
|
||||
|
||||
def close_position(self, symbol: str, side: str, size: float) -> dict:
|
||||
"""平仓"""
|
||||
# POST /api/mix/v1/order/closePosition
|
||||
|
||||
def get_position(self, symbol: str = None) -> list:
|
||||
"""查询持仓"""
|
||||
# GET /api/mix/v1/position/allPosition
|
||||
|
||||
def get_balance(self) -> dict:
|
||||
"""查询余额"""
|
||||
# GET /api/mix/v1/account/account
|
||||
|
||||
def set_leverage(self, symbol: str, leverage: int) -> dict:
|
||||
"""设置杠杆"""
|
||||
# POST /api/mix/v1/account/setLeverage
|
||||
```
|
||||
|
||||
#### 2.2 创建 `RealTradingService` 类
|
||||
|
||||
**文件**: `backend/app/services/real_trading_service.py`
|
||||
|
||||
**核心方法**:
|
||||
```python
|
||||
class RealTradingService:
|
||||
"""实盘交易服务 - 接口与 PaperTradingService 保持一致"""
|
||||
|
||||
def create_order_from_signal(self, signal: Dict, current_price: float) -> Dict:
|
||||
"""从信号创建实盘订单"""
|
||||
# 1. 风险检查(仓位、杠杆、余额)
|
||||
# 2. 调用 BitgetTradingAPI.place_order()
|
||||
# 3. 记录订单到数据库
|
||||
# 4. 发送通知
|
||||
|
||||
def check_price_triggers(self, symbol: str, price: float) -> List:
|
||||
"""检查止盈止损(从 Bitget 获取持仓状态)"""
|
||||
# 1. 获取实际持仓状态
|
||||
# 2. 判断是否触发止盈止损
|
||||
# 3. 调用平仓 API
|
||||
|
||||
def get_account_status(self) -> Dict:
|
||||
"""获取账户状态"""
|
||||
# 调用 Bitget API 获取真实余额和持仓
|
||||
```
|
||||
|
||||
### 阶段三:集成与切换 (1-2天)
|
||||
|
||||
#### 3.1 配置开关设计
|
||||
|
||||
```python
|
||||
# config.py
|
||||
class Settings(BaseSettings):
|
||||
# 交易模式选择
|
||||
trading_mode: str = "paper" # "paper" 或 "real"
|
||||
|
||||
# 模拟交易配置(保留)
|
||||
paper_trading_enabled: bool = True
|
||||
|
||||
# 实盘交易配置
|
||||
real_trading_enabled: bool = False
|
||||
|
||||
# Bitget API 配置
|
||||
bitget_api_key: str = ""
|
||||
bitget_api_secret: str = ""
|
||||
bitget_use_testnet: bool = True
|
||||
```
|
||||
|
||||
#### 3.2 服务切换逻辑
|
||||
|
||||
```python
|
||||
# crypto_agent.py 或根据 trading_mode 动态选择
|
||||
if settings.trading_mode == "paper":
|
||||
from app.services.paper_trading_service import get_paper_trading_service
|
||||
trading_service = get_paper_trading_service()
|
||||
else:
|
||||
from app.services.real_trading_service import get_real_trading_service
|
||||
trading_service = get_real_trading_service()
|
||||
```
|
||||
|
||||
#### 3.3 修改的文件清单
|
||||
|
||||
| 文件 | 修改内容 |
|
||||
|------|---------|
|
||||
| `config.py` | 添加实盘交易配置项 |
|
||||
| `crypto_agent.py` | 添加交易模式选择逻辑 |
|
||||
| `main.py` | 价格监控适配实盘交易 |
|
||||
| `api/paper_trading.py` | 添加实盘交易 API 端点 |
|
||||
|
||||
### 阶段四:测试验证 (2-3天)
|
||||
|
||||
#### 4.1 测试网测试
|
||||
- [ ] 下单功能测试
|
||||
- [ ] 撤单功能测试
|
||||
- [ ] 平仓功能测试
|
||||
- [ ] 持仓查询测试
|
||||
- [ ] 止盈止损触发测试
|
||||
|
||||
#### 4.2 小资金实盘测试
|
||||
- [ ] 使用小额资金(如 100 USDT)
|
||||
- [ ] 执行 10-20 笔交易
|
||||
- [ ] 验证盈亏计算准确性
|
||||
- [ ] 验证通知及时性
|
||||
|
||||
#### 4.3 压力测试
|
||||
- [ ] 极端行情下系统稳定性
|
||||
- [ ] API 限频处理
|
||||
- [ ] 网络异常恢复
|
||||
- [ ] 订单状态同步
|
||||
|
||||
---
|
||||
|
||||
## 五、风险控制
|
||||
|
||||
### 5.1 技术风险
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
|------|------|---------|
|
||||
| API 故障 | 无法交易 | 多重异常处理 + 飞书告警 |
|
||||
| 网络延迟 | 滑点增大 | 限价单为主,设置合理价格偏差 |
|
||||
| 订单状态不同步 | 重复开仓 | 定期同步持仓状态 |
|
||||
| 限频被封 | 暂停交易 | 请求频率控制 + 重试机制 |
|
||||
|
||||
### 5.2 交易风险
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
|------|------|---------|
|
||||
| 策略失效 | 亏损 | 测试网充分验证 + 小资金试运行 |
|
||||
| 极端行情 | 爆仓 | 严格止损 + 仓位控制 |
|
||||
| 误操作 | 意外亏损 | 双重确认 + 实盘交易前测试 |
|
||||
| API 泄露 | 资金风险 | IP 白名单 + 只读权限分离 |
|
||||
|
||||
### 5.3 资金管理建议
|
||||
|
||||
```python
|
||||
# 资金管理配置
|
||||
MAX_ACCOUNT_RATIO = 0.5 # 最大使用账户50%资金
|
||||
MAX_SINGLE_POSITION = 0.1 # 单笔最大10%仓位
|
||||
MAX_TOTAL_POSITION = 0.3 # 总持仓最大30%
|
||||
DEFAULT_LEVERAGE = 10 # 实盘杠杆低于模拟(20x)
|
||||
STOP_LOSS_PERCENT = 0.02 # 每笔最大亏损2%
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、监控与告警
|
||||
|
||||
### 6.1 必需监控指标
|
||||
|
||||
```python
|
||||
# 实时监控
|
||||
- 账户余额变化
|
||||
- 持仓盈亏实时状态
|
||||
- 订单执行状态
|
||||
- API 调用成功率
|
||||
- 系统响应时间
|
||||
```
|
||||
|
||||
### 6.2 告警机制
|
||||
|
||||
```python
|
||||
# 飞书告警场景
|
||||
- 订单执行失败
|
||||
- 止损/止盈触发
|
||||
- 账户余额异常变化
|
||||
- API 连接失败
|
||||
- 系统异常错误
|
||||
```
|
||||
|
||||
### 6.3 日志记录
|
||||
|
||||
```python
|
||||
# 实盘交易必需日志
|
||||
- 每笔订单的完整生命周期
|
||||
- API 请求和响应记录
|
||||
- 价格监控日志
|
||||
- 错误和异常详情
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、实施时间表
|
||||
|
||||
| 阶段 | 任务 | 预计时间 | 依赖 |
|
||||
|------|------|---------|------|
|
||||
| **阶段一** | 准备工作 | 1-2天 | - |
|
||||
| **阶段二** | 实盘服务开发 | 2-3天 | 阶段一完成 |
|
||||
| **阶段三** | 集成与切换 | 1-2天 | 阶段二完成 |
|
||||
| **阶段四** | 测试验证 | 2-3天 | 阶段三完成 |
|
||||
| **总计** | | **6-10天** | |
|
||||
|
||||
---
|
||||
|
||||
## 八、关键决策点
|
||||
|
||||
### 8.1 必须解决的问题
|
||||
|
||||
1. ✅ **订单映射**
|
||||
- 模拟交易订单 ↔ Bitget 实盘订单 ID
|
||||
- 状态同步机制
|
||||
|
||||
2. ✅ **持仓同步**
|
||||
- 定期从 Bitget 获取实际持仓
|
||||
- 与本地数据库保持一致
|
||||
|
||||
3. ✅ **止损策略**
|
||||
- 模拟交易:本地价格轮询检查
|
||||
- 实盘交易:可使用 Bitget 条件单 API
|
||||
|
||||
4. ✅ **错误处理**
|
||||
- API 失败时的降级策略
|
||||
- 部分成交处理
|
||||
|
||||
### 8.2 可选优化
|
||||
|
||||
1. ⭕ **条件单 API**
|
||||
- 使用 Bitget 条件单实现止损止盈
|
||||
- 减少本地轮询,降低 API 调用
|
||||
|
||||
2. ⭕ **WebSocket 推送**
|
||||
- 使用 Bitget WebSocket 实时获取成交和持仓更新
|
||||
- 降低延迟
|
||||
|
||||
3. ⭕ **仓位管理优化**
|
||||
- 根据账户余额动态调整仓位
|
||||
- 凯利公式资金管理
|
||||
|
||||
---
|
||||
|
||||
## 九、推荐实施顺序
|
||||
|
||||
### 第一步:最小化实盘功能 (3-4天)
|
||||
- ✅ 实现 `BitgetTradingAPI` 基础下单/撤单/平仓
|
||||
- ✅ 实现 `RealTradingService` 核心功能
|
||||
- ✅ 测试网验证
|
||||
|
||||
### 第二步:完整集成 (2-3天)
|
||||
- ✅ 添加配置开关
|
||||
- ✅ 修改现有调用逻辑
|
||||
- ✅ 数据库适配
|
||||
|
||||
### 第三步:生产验证 (持续)
|
||||
- ✅ 小资金试运行
|
||||
- ✅ 监控告警完善
|
||||
- ✅ 逐步增加资金规模
|
||||
|
||||
---
|
||||
|
||||
## 十、总结与建议
|
||||
|
||||
### ✅ 可行性评估
|
||||
|
||||
| 方面 | 评估 | 说明 |
|
||||
|------|------|------|
|
||||
| **技术可行性** | ✅ 高 | Bitget API 完善,所有功能都支持 |
|
||||
| **实施复杂度** | ⚠️ 中 | 需要处理订单同步、状态管理 |
|
||||
| **风险可控性** | ✅ 高 | 测试网 + 小资金 + 双重确认 |
|
||||
| **时间投入** | ⚠️ 中 | 6-10天完整实施 |
|
||||
|
||||
### ⚠️ 重要提醒
|
||||
|
||||
1. **先在测试网充分验证**
|
||||
- 所有功能都在测试网验证通过
|
||||
- 模拟各种异常情况
|
||||
|
||||
2. **小资金试运行**
|
||||
- 初期使用最小可交易资金
|
||||
- 验证1-2周后再逐步增加
|
||||
|
||||
3. **保留模拟交易**
|
||||
- 双模式并存,随时可切回模拟
|
||||
- 用于策略回测和新功能验证
|
||||
|
||||
4. **严格风控**
|
||||
- 实盘杠杆低于模拟
|
||||
- 仓位控制更保守
|
||||
- 止损必须执行
|
||||
|
||||
### 📋 下一步行动
|
||||
|
||||
**如果你确认要实施实盘交易,建议按以下顺序进行**:
|
||||
|
||||
1. **立即**: 申请 Bitget 测试网账户和 API 密钥
|
||||
2. **第1天**: 创建 `BitgetTradingAPI` 基础类
|
||||
3. **第2-3天**: 实现核心交易功能(下单、撤单、平仓)
|
||||
4. **第4-5天**: 测试网验证所有功能
|
||||
5. **第6-7天**: 小资金实盘测试(100 USDT)
|
||||
6. **验证稳定后**: 逐步增加资金规模
|
||||
|
||||
---
|
||||
|
||||
**方案制定完成,等待确认后开始实施**
|
||||
258
docs/REAL_TRADING_SETUP_GUIDE.md
Normal file
258
docs/REAL_TRADING_SETUP_GUIDE.md
Normal file
@ -0,0 +1,258 @@
|
||||
# Bitget 实盘交易配置指南
|
||||
|
||||
## 一、获取 Bitget API 密钥
|
||||
|
||||
### 1.1 注册 Bitget 账户
|
||||
|
||||
1. 访问 [Bitget 官网](https://www.bitget.com/)
|
||||
2. 注册账户并完成 KYC 认证
|
||||
|
||||
### 1.2 开通合约交易
|
||||
|
||||
1. 登录后进入「合约」页面
|
||||
2. 完成合约交易风险测评
|
||||
3. 开通 U 本位合约交易
|
||||
|
||||
### 1.3 创建 API 密钥
|
||||
|
||||
1. 进入「账户」→「API 管理」
|
||||
2. 点击「创建 API」
|
||||
3. 设置以下内容:
|
||||
- **备注名**: Stock Agent
|
||||
- **权限**:
|
||||
- ✅ 读取
|
||||
- ✅ 提现 (可关闭)
|
||||
- ✅ 交易 (必需)
|
||||
- **IP 白名单**: 添加你的服务器 IP(可选但推荐)
|
||||
- **Google 验证**: 完成绑定
|
||||
|
||||
4. 创建后获得:
|
||||
- **API Key**
|
||||
- **API Secret**
|
||||
- **Passphrase** (如果设置了)
|
||||
|
||||
---
|
||||
|
||||
## 二、配置项目
|
||||
|
||||
### 2.1 编辑 `.env` 文件
|
||||
|
||||
在项目根目录的 `.env` 文件中添加以下配置:
|
||||
|
||||
```bash
|
||||
# ============================================================================
|
||||
# Bitget API 配置
|
||||
# ============================================================================
|
||||
BITGET_API_KEY=你的API_Key
|
||||
BITGET_API_SECRET=你的API_Secret
|
||||
BITGET_PASSPHRASE=你的Passphrase
|
||||
|
||||
# 使用测试网(强烈建议先在测试网测试!)
|
||||
BITGET_USE_TESTNET=true
|
||||
|
||||
# 实盘交易总开关(false 时仅模拟交易生效)
|
||||
REAL_TRADING_ENABLED=false
|
||||
|
||||
# 风险控制参数
|
||||
REAL_TRADING_MAX_SINGLE_POSITION=1000
|
||||
REAL_TRADING_MAX_TOTAL_RATIO=0.5
|
||||
REAL_TRADING_DEFAULT_LEVERAGE=10
|
||||
REAL_TRADING_RISK_PER_TRADE=0.02
|
||||
REAL_TRADING_MAX_ORDERS=5
|
||||
```
|
||||
|
||||
### 2.2 配置项说明
|
||||
|
||||
| 配置项 | 说明 | 推荐值 |
|
||||
|--------|------|--------|
|
||||
| `BITGET_API_KEY` | Bitget API Key | 从 Bitget 获取 |
|
||||
| `BITGET_API_SECRET` | Bitget API Secret | 从 Bitget 获取 |
|
||||
| `BITGET_PASSPHRASE` | API Passphrase | 从 Bitget 获取(如果需要) |
|
||||
| `BITGET_USE_TESTNET` | 是否使用测试网 | `true`(测试网) |
|
||||
| `REAL_TRADING_ENABLED` | 实盘交易开关 | `false`(测试前保持 false) |
|
||||
| `REAL_TRADING_MAX_SINGLE_POSITION` | 单笔最大持仓 | `1000` USDT |
|
||||
| `REAL_TRADING_DEFAULT_LEVERAGE` | 默认杠杆 | `10`x(低于模拟的20x) |
|
||||
| `REAL_TRADING_RISK_PER_TRADE` | 每笔风险 | `0.02` (2%) |
|
||||
|
||||
---
|
||||
|
||||
## 三、测试网测试
|
||||
|
||||
### 3.1 Bitget 测试网
|
||||
|
||||
**测试网地址**: https://testnet.bitget.com/
|
||||
|
||||
**特点**:
|
||||
- 与生产网相同的 API 接口
|
||||
- 虚拟资金,无风险
|
||||
- 适合测试所有功能
|
||||
|
||||
### 3.2 配置测试网
|
||||
|
||||
确保 `.env` 中设置:
|
||||
```bash
|
||||
BITGET_USE_TESTNET=true
|
||||
```
|
||||
|
||||
### 3.3 运行测试脚本
|
||||
|
||||
```bash
|
||||
# 激活虚拟环境
|
||||
source backend/venv/bin/activate
|
||||
|
||||
# 运行测试脚本
|
||||
python scripts/test_real_trading.py
|
||||
```
|
||||
|
||||
### 3.4 测试内容
|
||||
|
||||
测试脚本会依次执行:
|
||||
1. ✅ API 连接测试
|
||||
2. ✅ 查询账户余额
|
||||
3. ✅ 查询当前持仓
|
||||
4. ⚠️ 下单测试(已注释,需手动取消注释)
|
||||
|
||||
---
|
||||
|
||||
## 四、生产网注意事项
|
||||
|
||||
### 4.1 切换到生产网
|
||||
|
||||
⚠️ **警告**: 切换到生产网后,所有交易都是真实资金!
|
||||
|
||||
1. 修改 `.env`:
|
||||
```bash
|
||||
BITGET_USE_TESTNET=false
|
||||
```
|
||||
|
||||
2. **强烈建议**:
|
||||
- 先在测试网充分测试所有功能
|
||||
- 使用最小资金进行初期测试
|
||||
- 设置严格的风险控制参数
|
||||
- 保留模拟交易作为回测
|
||||
|
||||
### 4.2 风险控制建议
|
||||
|
||||
```bash
|
||||
# 保守配置(推荐初期使用)
|
||||
REAL_TRADING_MAX_SINGLE_POSITION=100 # 单笔最大 100 USDT
|
||||
REAL_TRADING_DEFAULT_LEVERAGE=5 # 杠杆降低到 5x
|
||||
REAL_TRADING_RISK_PER_TRADE=0.01 # 每笔风险 1%
|
||||
REAL_TRADING_MAX_ORDERS=2 # 最多 2 个持仓
|
||||
```
|
||||
|
||||
### 4.3 实盘交易检查清单
|
||||
|
||||
- [ ] API Key 配置正确
|
||||
- [ ] 测试网所有功能验证通过
|
||||
- [ ] 风险参数已设置
|
||||
- [ ] 小资金试运行(100-1000 USDT)
|
||||
- [ ] 监控告警已配置
|
||||
- [ ] 紧急撤单方案已准备
|
||||
|
||||
---
|
||||
|
||||
## 五、功能验证
|
||||
|
||||
### 5.1 API 连接验证
|
||||
|
||||
```bash
|
||||
python scripts/test_real_trading.py
|
||||
```
|
||||
|
||||
**期望输出**:
|
||||
```
|
||||
✅ API 连接成功,USDT 余额: xxxxx
|
||||
```
|
||||
|
||||
### 5.2 查询功能验证
|
||||
|
||||
测试脚本会自动验证:
|
||||
- ✅ 账户余额查询
|
||||
- ✅ 持仓查询
|
||||
- ✅ 订单状态查询
|
||||
|
||||
### 5.3 下单功能验证(测试网)
|
||||
|
||||
在测试网环境下,可以取消注释测试脚本中的下单代码进行真实下单测试:
|
||||
|
||||
```python
|
||||
# 在 test_real_trading.py 中找到此部分并取消注释
|
||||
result = api.place_order(
|
||||
symbol=symbol,
|
||||
side='open_long',
|
||||
order_type='market',
|
||||
size=test_size
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、故障排查
|
||||
|
||||
### 6.1 API 连接失败
|
||||
|
||||
**错误**: `API 连接失败`
|
||||
|
||||
**解决**:
|
||||
1. 检查 API Key 和 Secret 是否正确
|
||||
2. 检查是否在正确的网络(测试网/生产网)
|
||||
3. 检查 IP 白名单配置
|
||||
|
||||
### 6.2 签名验证失败
|
||||
|
||||
**错误**: `API 签名验证失败`
|
||||
|
||||
**解决**:
|
||||
1. 检查系统时间是否准确
|
||||
2. 检查 API Secret 是否完整复制
|
||||
3. 检查 passphrase 是否正确
|
||||
|
||||
### 6.3 权限不足
|
||||
|
||||
**错误**: `权限不足`
|
||||
|
||||
**解决**:
|
||||
1. 确认 API Key 已开通「交易」权限
|
||||
2. 重新创建 API Key 并勾选所有必需权限
|
||||
|
||||
---
|
||||
|
||||
## 七、安全最佳实践
|
||||
|
||||
### 7.1 API Key 安全
|
||||
|
||||
- ✅ 不要将 API Key 提交到 Git 仓库
|
||||
- ✅ 使用 `.env` 文件并添加到 `.gitignore`
|
||||
- ✅ 定期轮换 API Key
|
||||
- ✅ 设置 IP 白名单
|
||||
|
||||
### 7.2 资金安全
|
||||
|
||||
- ✅ 使用测试网充分测试
|
||||
- ✅ 从小资金开始
|
||||
- ✅ 设置合理的止损
|
||||
- ✅ 不要使用全部资金
|
||||
|
||||
### 7.3 系统安全
|
||||
|
||||
- ✅ 保留实盘交易日志
|
||||
- ✅ 配置飞书/Telegram 告警
|
||||
- ✅ 定期检查持仓状态
|
||||
- ✅ 设置紧急停机方案
|
||||
|
||||
---
|
||||
|
||||
## 八、下一步
|
||||
|
||||
配置完成后:
|
||||
|
||||
1. ✅ 运行测试脚本验证连接
|
||||
2. ✅ 在测试网测试所有功能
|
||||
3. ✅ 小资金生产网测试(可选)
|
||||
4. ✅ 配置监控告警
|
||||
5. ✅ 逐步增加资金规模
|
||||
|
||||
---
|
||||
|
||||
**祝交易顺利!** 🚀
|
||||
170
scripts/test_bitget_sdk.py
Normal file
170
scripts/test_bitget_sdk.py
Normal file
@ -0,0 +1,170 @@
|
||||
"""
|
||||
测试 Bitget CCXT SDK 实现
|
||||
|
||||
在运行之前,请确保:
|
||||
1. 已安装 ccxt: pip install ccxt
|
||||
2. 已在 .env 文件中配置 Bitget API 密钥
|
||||
3. 使用测试网进行测试(BITGET_USE_TESTNET=true)
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目路径
|
||||
script_dir = Path(__file__).parent
|
||||
project_root = script_dir.parent
|
||||
backend_dir = project_root / "backend"
|
||||
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# 设置工作目录
|
||||
os.chdir(project_root)
|
||||
|
||||
from app.services.bitget_trading_api_sdk import BitgetTradingAPI
|
||||
from app.config import get_settings
|
||||
from app.utils.logger import logger
|
||||
|
||||
|
||||
def test_connection():
|
||||
"""测试 API 连接"""
|
||||
logger.info("=" * 60)
|
||||
logger.info("测试 1: API 连接测试")
|
||||
logger.info("=" * 60)
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
api = BitgetTradingAPI(
|
||||
api_key=settings.bitget_api_key,
|
||||
api_secret=settings.bitget_api_secret,
|
||||
passphrase=settings.bitget_passphrase,
|
||||
use_testnet=settings.bitget_use_testnet
|
||||
)
|
||||
|
||||
result = api.test_connection()
|
||||
logger.info(f"连接测试结果: {'✅ 成功' if result else '❌ 失败'}")
|
||||
|
||||
return api if result else None
|
||||
|
||||
|
||||
def test_get_balance(api):
|
||||
"""测试查询余额"""
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info("测试 2: 查询账户余额")
|
||||
logger.info("=" * 60)
|
||||
|
||||
balance = api.get_balance()
|
||||
|
||||
if balance:
|
||||
logger.info(f"✅ 余额查询成功")
|
||||
for currency, info in balance.items():
|
||||
available = info.get('available', '0')
|
||||
frozen = info.get('frozen', '0')
|
||||
logger.info(f" {currency}: 可用={available}, 冻结={frozen}")
|
||||
else:
|
||||
logger.error("❌ 余额查询失败")
|
||||
|
||||
|
||||
def test_get_position(api):
|
||||
"""测试查询持仓"""
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info("测试 3: 查询持仓")
|
||||
logger.info("=" * 60)
|
||||
|
||||
positions = api.get_position()
|
||||
|
||||
if positions:
|
||||
logger.info(f"✅ 持仓查询成功,共 {len(positions)} 个持仓")
|
||||
for pos in positions:
|
||||
symbol = pos.get('symbol', 'N/A')
|
||||
size = pos.get('contracts', 0)
|
||||
side = pos.get('side', 'N/A')
|
||||
unrealized_pnl = pos.get('unrealizedPnl', 0)
|
||||
logger.info(f" {symbol} {side} {size}张 (未实现盈亏: {unrealized_pnl})")
|
||||
else:
|
||||
logger.info(" 当前无持仓")
|
||||
|
||||
|
||||
def test_get_open_orders(api):
|
||||
"""测试查询挂单"""
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info("测试 4: 查询当前挂单")
|
||||
logger.info("=" * 60)
|
||||
|
||||
orders = api.get_open_orders()
|
||||
|
||||
if orders:
|
||||
logger.info(f"✅ 挂单查询成功,共 {len(orders)} 个挂单")
|
||||
for order in orders[:5]: # 只显示前 5 个
|
||||
symbol = order.get('symbol', 'N/A')
|
||||
side = order.get('side', 'N/A')
|
||||
price = order.get('price', 'N/A')
|
||||
amount = order.get('amount', 'N/A')
|
||||
logger.info(f" {symbol} {side} @ {price} x {amount}")
|
||||
else:
|
||||
logger.info(" 当前无挂单")
|
||||
|
||||
|
||||
def test_market_ticker(api, symbol='BTC/USDT:USDT'):
|
||||
"""测试获取市场行情"""
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info(f"测试 5: 获取 {symbol} 市场行情")
|
||||
logger.info("=" * 60)
|
||||
|
||||
try:
|
||||
ticker = api.exchange.fetch_ticker(symbol)
|
||||
if ticker:
|
||||
logger.info(f"✅ 行情获取成功")
|
||||
logger.info(f" 交易对: {symbol}")
|
||||
logger.info(f" 最新价: {ticker.get('last', 'N/A')}")
|
||||
logger.info(f" 24h涨跌: {ticker.get('percentage', 'N/A')}%")
|
||||
logger.info(f" 24h成交量: {ticker.get('baseVolume', 'N/A')}")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 行情获取失败: {e}")
|
||||
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
logger.info("🚀 开始测试 Bitget CCXT SDK 实现")
|
||||
|
||||
# 检查配置
|
||||
settings = get_settings()
|
||||
if not settings.bitget_api_key or not settings.bitget_api_secret:
|
||||
logger.error("❌ 未配置 Bitget API 密钥,请在 .env 文件中设置:")
|
||||
logger.error(" BITGET_API_KEY=your_api_key")
|
||||
logger.error(" BITGET_API_SECRET=your_api_secret")
|
||||
return
|
||||
|
||||
# 测试连接
|
||||
api = test_connection()
|
||||
if not api:
|
||||
logger.error("❌ API 连接失败,请检查:")
|
||||
logger.error(" 1. API 密钥是否正确")
|
||||
logger.error(" 2. 网络连接是否正常")
|
||||
logger.error(" 3. 测试网是否可用")
|
||||
return
|
||||
|
||||
# 运行测试
|
||||
try:
|
||||
test_get_balance(api)
|
||||
test_get_position(api)
|
||||
test_get_open_orders(api)
|
||||
test_market_ticker(api)
|
||||
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info("✅ 所有测试完成")
|
||||
logger.info("=" * 60)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 测试过程中出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
finally:
|
||||
# 关闭连接
|
||||
api.close()
|
||||
logger.info("\n🔌 API 连接已关闭")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
133
scripts/test_bitget_v2.py
Normal file
133
scripts/test_bitget_v2.py
Normal file
@ -0,0 +1,133 @@
|
||||
"""
|
||||
测试 Bitget V2 API
|
||||
"""
|
||||
import sys
|
||||
sys.path.insert(0, '/Users/aaron/source_code/Stock_Agent')
|
||||
|
||||
from backend.app.services.bitget_trading_api import get_bitget_trading_api
|
||||
from backend.app.utils.logger import logger
|
||||
|
||||
|
||||
def test_bitget_v2():
|
||||
"""测试 Bitget V2 API 各个功能"""
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("测试 Bitget V2 API")
|
||||
print("="*60)
|
||||
|
||||
# 获取 API 实例
|
||||
api = get_bitget_trading_api()
|
||||
|
||||
if not api:
|
||||
print("❌ 无法初始化 Bitget API(请检查 .env 配置)")
|
||||
return False
|
||||
|
||||
print(f"\n📡 API 端点: {api.base_url}")
|
||||
print(f"🔑 API Key: {api.api_key[:10]}...{api.api_key[-4:]}")
|
||||
|
||||
# 测试 1: 连接测试和账户余额
|
||||
print("\n" + "-"*60)
|
||||
print("测试 1: 账户余额查询")
|
||||
print("-"*60)
|
||||
|
||||
try:
|
||||
balance = api.get_balance()
|
||||
if balance:
|
||||
usdt = balance.get('USDT', {})
|
||||
print(f"✅ USDT 可用余额: {usdt.get('available', '0')}")
|
||||
print(f" 冻结: {usdt.get('frozen', '0')}")
|
||||
print(f" 锁定: {usdt.get('locked', '0')}")
|
||||
else:
|
||||
print("⚠️ 余额查询返回空数据")
|
||||
except Exception as e:
|
||||
print(f"❌ 余额查询失败: {e}")
|
||||
return False
|
||||
|
||||
# 测试 2: 账户信息
|
||||
print("\n" + "-"*60)
|
||||
print("测试 2: 账户信息查询")
|
||||
print("-"*60)
|
||||
|
||||
try:
|
||||
account_info = api.get_account_info()
|
||||
if account_info:
|
||||
print(f"✅ 账户信息: {list(account_info.keys())}")
|
||||
else:
|
||||
print("⚠️ 账户信息查询返回空数据")
|
||||
except Exception as e:
|
||||
print(f"❌ 账户信息查询失败: {e}")
|
||||
|
||||
# 测试 3: 查询持仓
|
||||
print("\n" + "-"*60)
|
||||
print("测试 3: 持仓查询")
|
||||
print("-"*60)
|
||||
|
||||
try:
|
||||
positions = api.get_position()
|
||||
if positions:
|
||||
print(f"✅ 当前持仓数: {len(positions)}")
|
||||
for pos in positions:
|
||||
symbol = pos.get('symbol', 'N/A')
|
||||
hold_side = pos.get('holdSide', 'N/A')
|
||||
total = pos.get('total', '0')
|
||||
leverage = pos.get('leverage', 'N/A')
|
||||
print(f" {symbol} {hold_side}: {total} 张 (杠杆 {leverage}x)")
|
||||
else:
|
||||
print("✅ 当前无持仓")
|
||||
except Exception as e:
|
||||
print(f"❌ 持仓查询失败: {e}")
|
||||
return False
|
||||
|
||||
# 测试 4: 查询当前挂单
|
||||
print("\n" + "-"*60)
|
||||
print("测试 4: 当前挂单查询")
|
||||
print("-"*60)
|
||||
|
||||
try:
|
||||
open_orders = api.get_open_orders()
|
||||
if open_orders:
|
||||
print(f"✅ 当前挂单数: {len(open_orders)}")
|
||||
for order in open_orders[:5]: # 只显示前5个
|
||||
symbol = order.get('symbol', 'N/A')
|
||||
side = order.get('side', 'N/A')
|
||||
size = order.get('size', '0')
|
||||
price = order.get('price', '市价')
|
||||
print(f" {symbol} {side} {size} @ {price}")
|
||||
if len(open_orders) > 5:
|
||||
print(f" ... 还有 {len(open_orders) - 5} 个挂单")
|
||||
else:
|
||||
print("✅ 当前无挂单")
|
||||
except Exception as e:
|
||||
print(f"❌ 挂单查询失败: {e}")
|
||||
return False
|
||||
|
||||
# 测试 5: 查询历史订单
|
||||
print("\n" + "-"*60)
|
||||
print("测试 5: 历史订单查询 (BTCUSDT)")
|
||||
print("-"*60)
|
||||
|
||||
try:
|
||||
history_orders = api.get_history_orders('BTCUSDT', limit=10)
|
||||
if history_orders:
|
||||
print(f"✅ 最近 {len(history_orders)} 条历史订单")
|
||||
for order in history_orders[:3]: # 只显示前3个
|
||||
order_id = order.get('orderId', 'N/A')
|
||||
side = order.get('side', 'N/A')
|
||||
order_type = order.get('orderType', 'N/A')
|
||||
state = order.get('state', 'N/A')
|
||||
print(f" 订单 {order_id}: {side} {order_type} - {state}")
|
||||
else:
|
||||
print("✅ 暂无历史订单")
|
||||
except Exception as e:
|
||||
print(f"❌ 历史订单查询失败: {e}")
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("✅ 所有核心功能测试完成!V2 API 工作正常")
|
||||
print("="*60 + "\n")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = test_bitget_v2()
|
||||
sys.exit(0 if success else 1)
|
||||
285
scripts/test_real_trading.py
Normal file
285
scripts/test_real_trading.py
Normal file
@ -0,0 +1,285 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
实盘交易功能测试脚本
|
||||
|
||||
测试 Bitget 实盘交易 API 的各项功能
|
||||
⚠️ 注意: 此脚本会进行真实交易,请确保在测试网环境运行!
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root / "backend"))
|
||||
|
||||
from app.config import get_settings
|
||||
from app.services.bitget_trading_api import BitgetTradingAPI, get_bitget_trading_api
|
||||
from app.services.real_trading_service import get_real_trading_service
|
||||
|
||||
|
||||
def print_section(title: str):
|
||||
"""打印分节标题"""
|
||||
print("\n" + "=" * 80)
|
||||
print(f" {title}")
|
||||
print("=" * 80)
|
||||
|
||||
|
||||
def test_api_connection():
|
||||
"""测试 API 连接"""
|
||||
print_section("1. 测试 API 连接")
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
print(f"\n配置信息:")
|
||||
print(f" API Key: {settings.bitget_api_key[:10]}..." if settings.bitget_api_key else " API Key: 未配置")
|
||||
print(f" 测试网: {'是 ✅' if settings.bitget_use_testnet else '否 ⚠️'}")
|
||||
|
||||
if not settings.bitget_api_key or not settings.bitget_api_secret:
|
||||
print("\n❌ API Key 未配置,请在 .env 文件中设置:")
|
||||
print(" BITGET_API_KEY=your_api_key")
|
||||
print(" BITGET_API_SECRET=your_api_secret")
|
||||
print(" BITGET_PASSPHRASE=your_passphrase")
|
||||
print(" BITGET_USE_TESTNET=true")
|
||||
return False
|
||||
|
||||
# 创建 API 实例
|
||||
api = BitgetTradingAPI(
|
||||
api_key=settings.bitget_api_key,
|
||||
api_secret=settings.bitget_api_secret,
|
||||
passphrase=settings.bitget_passphrase,
|
||||
use_testnet=settings.bitget_use_testnet
|
||||
)
|
||||
|
||||
# 测试连接
|
||||
if api.test_connection():
|
||||
print("\n✅ API 连接成功!")
|
||||
return True
|
||||
else:
|
||||
print("\n❌ API 连接失败!")
|
||||
print(" 请检查:")
|
||||
print(" 1. API Key 和 Secret 是否正确")
|
||||
print(" 2. 是否在测试网环境")
|
||||
print(" 3. API 权限是否正确")
|
||||
return False
|
||||
|
||||
|
||||
def test_query_account():
|
||||
"""测试查询账户"""
|
||||
print_section("2. 测试查询账户")
|
||||
|
||||
api = get_bitget_trading_api()
|
||||
if not api:
|
||||
print("\n❌ 交易 API 未初始化")
|
||||
return False
|
||||
|
||||
# 查询余额
|
||||
print("\n查询账户余额...")
|
||||
balance = api.get_balance()
|
||||
|
||||
if balance:
|
||||
usdt_info = balance.get('USDT', {})
|
||||
print(f"\n✅ USDT 余额:")
|
||||
print(f" 可用: ${float(usdt_info.get('available', 0)):,.2f}")
|
||||
print(f" 冻结: ${float(usdt_info.get('frozen', 0)):,.2f}")
|
||||
print(f" 锁定: ${float(usdt_info.get('locked', 0)):,.2f}")
|
||||
return True
|
||||
else:
|
||||
print("\n❌ 查询余额失败")
|
||||
return False
|
||||
|
||||
|
||||
def test_query_position():
|
||||
"""测试查询持仓"""
|
||||
print_section("3. 测试查询持仓")
|
||||
|
||||
api = get_bitget_trading_api()
|
||||
if not api:
|
||||
print("\n❌ 交易 API 未初始化")
|
||||
return False
|
||||
|
||||
# 查询持仓
|
||||
print("\n查询当前持仓...")
|
||||
positions = api.get_position()
|
||||
|
||||
print(f"\n✅ 当前持仓数量: {len(positions)}")
|
||||
|
||||
if positions:
|
||||
for pos in positions:
|
||||
symbol = pos.get('symbol', 'N/A')
|
||||
total = pos.get('total', 0)
|
||||
available = pos.get('available', 0)
|
||||
hold_side = pos.get('holdSide', 'N/A')
|
||||
avg_price = pos.get('averageOpenPrice', 0)
|
||||
|
||||
print(f"\n {symbol} ({hold_side})")
|
||||
print(f" 持仓: {total}")
|
||||
print(f" 可用: {available}")
|
||||
print(f" 均价: ${avg_price}")
|
||||
else:
|
||||
print(" 无持仓")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def test_place_small_order():
|
||||
"""
|
||||
测试下单(小仓位测试)
|
||||
|
||||
⚠️ 警告: 此函数会进行真实交易!
|
||||
建议在测试网运行,并使用最小仓位
|
||||
"""
|
||||
print_section("4. 测试下单 (⚠️ 实盘交易)")
|
||||
|
||||
# 确认是否要测试
|
||||
print("\n⚠️ 警告: 此测试会进行真实下单!")
|
||||
print(" 建议: 1. 确保在测试网环境")
|
||||
print(" 2. 使用最小仓位测试")
|
||||
print(" 3. 先手动撤销测试订单")
|
||||
|
||||
api = get_bitget_trading_api()
|
||||
if not api:
|
||||
print("\n❌ 交易 API 未初始化")
|
||||
return False
|
||||
|
||||
# 使用 BTCUSDT 进行测试
|
||||
symbol = "BTCUSDT"
|
||||
|
||||
# 查询当前价格
|
||||
from app.services.bitget_service import bitget_service
|
||||
current_price = bitget_service.get_current_price(symbol)
|
||||
|
||||
if not current_price:
|
||||
print(f"\n❌ 无法获取 {symbol} 当前价格")
|
||||
return False
|
||||
|
||||
print(f"\n{symbol} 当前价格: ${current_price:,.2f}")
|
||||
|
||||
# 使用最小数量(1张,通常约等于1 USDT)
|
||||
test_size = 1
|
||||
|
||||
# 使用市价单测试(快速成交)
|
||||
print(f"\n准备下测试单:")
|
||||
print(f" 交易对: {symbol}")
|
||||
print(f" 方向: 开多 (open_long)")
|
||||
print(f" 类型: 市价单")
|
||||
print(f" 数量: {test_size} 张 (约 ${test_size})")
|
||||
|
||||
# 取消注释以实际下单
|
||||
print("\n❗ 下单功能已禁用,避免意外交易")
|
||||
print(" 如需测试,请手动取消下面的注释并确认:")
|
||||
print("")
|
||||
|
||||
"""
|
||||
# 实际下单代码(已注释,需手动取消注释)
|
||||
result = api.place_order(
|
||||
symbol=symbol,
|
||||
side='open_long',
|
||||
order_type='market',
|
||||
size=test_size
|
||||
)
|
||||
|
||||
if result:
|
||||
order_id = result.get('orderId')
|
||||
print(f"\n✅ 下单成功!")
|
||||
print(f" 订单ID: {order_id}")
|
||||
print(f" 请在交易所检查订单状态")
|
||||
|
||||
# 询问是否立即撤单
|
||||
print("\n是否立即撤单?(测试用)")
|
||||
# input("按回车键撤单...")
|
||||
|
||||
# 撤单
|
||||
if api.cancel_order(symbol, order_id=order_id):
|
||||
print(f"\n✅ 撤单成功")
|
||||
else:
|
||||
print(f"\n❌ 撤单失败,请手动在交易所撤单")
|
||||
|
||||
return True
|
||||
else:
|
||||
print(f"\n❌ 下单失败")
|
||||
return False
|
||||
"""
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def test_real_trading_service():
|
||||
"""测试实盘交易服务"""
|
||||
print_section("5. 测试实盘交易服务")
|
||||
|
||||
service = get_real_trading_service()
|
||||
|
||||
if not service:
|
||||
print("\n❌ 实盘交易服务未启用")
|
||||
print(" 请在 .env 中设置:")
|
||||
print(" REAL_TRADING_ENABLED=true")
|
||||
return False
|
||||
|
||||
# 查询账户状态
|
||||
print("\n查询账户状态...")
|
||||
account = service.get_account_status()
|
||||
|
||||
print(f"\n✅ 账户状态:")
|
||||
print(f" 总余额: ${account['current_balance']:,.2f}")
|
||||
print(f" 可用: ${account['available']:,.2f}")
|
||||
print(f" 已用: ${account['used_margin']:,.2f}")
|
||||
print(f" 持仓: ${account['total_position_value']:,.2f}")
|
||||
|
||||
# 同步持仓
|
||||
print("\n同步交易所持仓...")
|
||||
positions = service.sync_positions_from_exchange()
|
||||
|
||||
print(f"\n✅ 交易所持仓: {len(positions)} 个")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("\n" + "🚀" * 40)
|
||||
print("\nBitget 实盘交易功能测试")
|
||||
print(f"测试时间: {datetime.now()}")
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
print("\n⚠️ 重要提醒:")
|
||||
print(f" 当前环境: {'测试网 ✅' if settings.bitget_use_testnet else '生产网 ⚠️'}")
|
||||
|
||||
try:
|
||||
# 1. 测试 API 连接
|
||||
if not test_api_connection():
|
||||
print("\n❌ API 连接测试失败,请检查配置")
|
||||
return
|
||||
|
||||
# 2. 测试查询账户
|
||||
if not test_query_account():
|
||||
print("\n❌ 查询账户测试失败")
|
||||
return
|
||||
|
||||
# 3. 测试查询持仓
|
||||
if not test_query_position():
|
||||
print("\n❌ 查询持仓测试失败")
|
||||
return
|
||||
|
||||
# 4. 测试下单(需要手动取消注释)
|
||||
# test_place_small_order()
|
||||
|
||||
# 5. 测试实盘交易服务
|
||||
if not test_real_trading_service():
|
||||
print("\n⚠️ 实盘交易服务未启用(可忽略)")
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print(" ✅ 所有测试通过!")
|
||||
print("=" * 80)
|
||||
print("\n实盘交易功能已就绪!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 测试出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from datetime import datetime
|
||||
main()
|
||||
Loading…
Reference in New Issue
Block a user