stock-ai-agent/backend/tests/test_bitget_live_integration.py
2026-03-25 23:28:48 +08:00

626 lines
22 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Bitget 真实 API 集成测试
⚠️ 警告:此测试会使用真实 API 调用和真实订单!
- 确保使用测试网或接受小额手续费
- 市价单会立即成交,产生实际盈亏
- 测试后检查是否有残留订单/持仓
运行方式:
cd backend
source venv/bin/activate
python3 tests/test_bitget_live_integration.py
"""
import os
import sys
import time
from datetime import datetime
# 添加项目路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from dotenv import load_dotenv
load_dotenv('/Users/aaron/source_code/Stock_Agent/.env')
from app.services.bitget_trading_api_sdk import BitgetTradingAPI
from app.services.bitget_live_trading_service import BitgetLiveTradingService
class BitgetIntegrationTest:
"""Bitget 真实 API 集成测试"""
def __init__(self):
self.api_key = os.getenv('BITGET_API_KEY')
self.api_secret = os.getenv('BITGET_API_SECRET')
self.passphrase = os.getenv('BITGET_PASSPHRASE')
self.use_testnet = os.getenv('BITGET_USE_TESTNET', 'true').lower() == 'true'
if not all([self.api_key, self.api_secret, self.passphrase]):
raise ValueError("❌ 请在 .env 中配置 BITGET_API_KEY, BITGET_API_SECRET, BITGET_PASSPHRASE")
print(f"\n{'='*60}")
print(f"Bitget 真实 API 集成测试")
print(f"{'='*60}")
print(f"API Key: {self.api_key[:10]}...")
print(f"测试网模式: {self.use_testnet}")
print(f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"{'='*60}\n")
# 初始化 API
self.api = BitgetTradingAPI(
api_key=self.api_key,
api_secret=self.api_secret,
passphrase=self.passphrase,
use_testnet=self.use_testnet
)
# 初始化 Service
self.service = BitgetLiveTradingService.__new__(BitgetLiveTradingService)
self.service.trading_api = self.api
self.service.max_single_position = 100
self.service.max_total_leverage = 5
self.service.circuit_breaker_drawdown = 0.10
self.service._initial_account_state = None
self.service.initial_balance = None # 添加这个属性
# 测试状态
self.limit_order_id = None
self.limit_order_symbol = None
self.market_entry_price = 0.0
# ==================== 基础连接测试 ====================
def test_01_connection(self):
"""测试 1: API 连接"""
print("\n📡 测试 1: API 连接")
print("-" * 40)
try:
# 获取服务器时间
server_time = self.api.exchange.fetch_time()
print(f"✅ 服务器时间: {datetime.fromtimestamp(server_time/1000)}")
# 加载市场信息
markets = self.api.exchange.load_markets()
btc_market = markets.get('BTC/USDT:USDT', {})
print(f"✅ BTC 市场ID: {btc_market.get('id', 'N/A')}")
return True
except Exception as e:
print(f"❌ 连接失败: {e}")
return False
def test_02_get_balance(self):
"""测试 2: 获取账户余额"""
print("\n💰 测试 2: 获取账户余额")
print("-" * 40)
try:
balance = self.api.get_balance()
if balance:
usdt = balance.get('USDT', {})
print(f"✅ USDT 可用: {usdt.get('free', 0):.2f}")
print(f"✅ USDT 冻结: {usdt.get('used', 0):.2f}")
print(f"✅ USDT 总额: {usdt.get('total', 0):.2f}")
return True
else:
print("❌ 余额为空")
return False
except Exception as e:
print(f"❌ 获取余额失败: {e}")
return False
def test_03_get_positions(self):
"""测试 3: 获取当前持仓"""
print("\n📊 测试 3: 获取当前持仓")
print("-" * 40)
try:
positions = self.api.get_position()
if positions:
active_positions = [p for p in positions if float(p.get('contracts', 0)) > 0]
print(f"✅ 当前持仓数: {len(active_positions)}")
for pos in active_positions:
symbol = pos.get('symbol', 'N/A')
contracts = float(pos.get('contracts', 0))
side = pos.get('side', 'N/A')
entry = float(pos.get('entryPrice', 0))
print(f" - {symbol}: {side} {contracts} 张 @ ${entry:,.2f}")
else:
print("✅ 当前无持仓")
return True
except Exception as e:
print(f"❌ 获取持仓失败: {e}")
return False
def test_04_set_leverage(self):
"""测试 4: 设置杠杆"""
print("\n⚙️ 测试 4: 设置杠杆")
print("-" * 40)
try:
symbol = 'BTCUSDT'
leverage = 5
self.api.set_leverage(symbol, leverage)
print(f"{symbol} 杠杆已设置为 {leverage}x")
return True
except Exception as e:
print(f"❌ 设置杠杆失败: {e}")
return False
# ==================== 限价单测试 ====================
def test_05_place_limit_order(self):
"""测试 5: 下限价单(挂单,预期不成交)"""
print("\n📝 测试 5: 下限价单")
print("-" * 40)
try:
symbol = 'BTCUSDT'
# 获取当前价格
ticker = self.api.exchange.fetch_ticker('BTC/USDT:USDT')
current_price = ticker['last']
print(f" 当前 BTC 价格: ${current_price:,.2f}")
# 计算挂单价格(当前价格 - 5%,预期不会成交)
limit_price = current_price * 0.95
print(f" 挂单价格: ${limit_price:,.2f} (低 5%,预期不成交)")
# 下 1 张多单(最小单位)
order = self.api.place_order(
symbol=symbol,
side='buy',
order_type='limit',
size=1, # 1 张 = 0.01 BTC
price=limit_price
)
if order:
self.limit_order_id = order.get('id')
self.limit_order_symbol = symbol
print(f"✅ 限价单已下: {self.limit_order_id}")
print(f" 订单状态: {order.get('status')}")
return True
else:
print("❌ 下单返回空")
return False
except Exception as e:
print(f"❌ 下限价单失败: {e}")
return False
def test_06_get_open_orders(self):
"""测试 6: 查询挂单"""
print("\n📋 测试 6: 查询挂单")
print("-" * 40)
try:
symbol = self.limit_order_symbol or 'BTCUSDT'
orders = self.api.get_open_orders(symbol)
if orders:
print(f"✅ 当前挂单数: {len(orders)}")
for order in orders:
order_id = order.get('id', 'N/A')
side = order.get('side', 'N/A')
price = order.get('price', 0)
amount = order.get('amount', 0)
print(f" - {order_id}: {side} {amount} @ ${price:,.2f}")
else:
print("⚠️ 无挂单")
return True
except Exception as e:
print(f"❌ 查询挂单失败: {e}")
return False
def test_07_cancel_order(self):
"""测试 7: 撤销挂单"""
print("\n❌ 测试 7: 撤销挂单")
print("-" * 40)
try:
if not self.limit_order_id:
print("⚠️ 没有需要撤销的订单")
return True
# 使用 cancel_all_orders 撤销该 symbol 所有挂单
success = self.api.cancel_all_orders(self.limit_order_symbol)
if success:
print(f"✅ 已撤销 {self.limit_order_symbol} 的所有挂单")
self.limit_order_id = None
return True
else:
print(f"❌ 撤单失败")
return False
except Exception as e:
print(f"❌ 撤销订单失败: {e}")
return False
# ==================== 市价单测试(真实成交!)====================
def test_08_market_order_open(self):
"""测试 8: 市价开多单(真实成交!)"""
print("\n⚡ 测试 8: 市价开多单")
print("-" * 40)
print("⚠️ 警告:此测试会产生真实订单和手续费!")
try:
symbol = 'BTCUSDT'
# 获取当前价格
ticker = self.api.exchange.fetch_ticker('BTC/USDT:USDT')
current_price = ticker['last']
print(f" 当前 BTC 价格: ${current_price:,.2f}")
# 下 1 张市价多单(最小单位 = 0.01 BTC
print(f" 下市价多单: 1 张 (0.01 BTC)...")
order = self.api.place_order(
symbol=symbol,
side='buy',
order_type='market',
size=1 # 1 张 = 0.01 BTC
)
if order:
order_id = order.get('id')
status = order.get('status')
# 安全获取 average 价格
avg_price = order.get('average') or order.get('price') or 0
if avg_price:
avg_price = float(avg_price)
print(f"✅ 市价单已成交: {order_id}")
print(f" 订单状态: {status}")
print(f" 成交均价: ${avg_price:,.2f}")
print(f" 成交数量: {order.get('filled', 0)}")
# 保存入场价
self.market_entry_price = avg_price
return True
else:
print("❌ 下单返回空")
return False
except Exception as e:
print(f"❌ 市价开仓失败: {e}")
import traceback
traceback.print_exc()
return False
def test_09_verify_position_opened(self):
"""测试 9: 验证持仓已开启"""
print("\n🔍 测试 9: 验证持仓")
print("-" * 40)
try:
positions = self.api.get_position('BTCUSDT')
if positions:
for pos in positions:
contracts = float(pos.get('contracts', 0))
if contracts > 0:
side = pos.get('side', 'N/A')
entry_price = float(pos.get('entryPrice', 0))
unrealized_pnl = float(pos.get('unrealizedPnl', 0))
print(f"✅ 持仓已确认:")
print(f" 方向: {side}")
print(f" 数量: {contracts}")
print(f" 开仓价: ${entry_price:,.2f}")
print(f" 未实现盈亏: ${unrealized_pnl:,.2f}")
return True
print("❌ 未找到持仓")
return False
except Exception as e:
print(f"❌ 验证持仓失败: {e}")
return False
def test_10_set_tp_sl(self):
"""测试 10: 设置止盈止损"""
print("\n🎯 测试 10: 设置止盈止损")
print("-" * 40)
try:
# 获取当前价格
ticker = self.api.exchange.fetch_ticker('BTC/USDT:USDT')
current_price = ticker['last']
# 设置止盈(+1%)和止损(-0.5%
tp_price = current_price * 1.01 # +1%
sl_price = current_price * 0.995 # -0.5%
print(f" 当前价格: ${current_price:,.2f}")
print(f" 设置止盈: ${tp_price:,.2f} (+1%)")
print(f" 设置止损: ${sl_price:,.2f} (-0.5%)")
result = self.api.modify_sl_tp(
symbol='BTCUSDT',
stop_loss=sl_price,
take_profit=tp_price
)
if result:
print(f"✅ 止盈止损已设置")
else:
print(f"⚠️ 止盈止损设置返回 False")
return True # 无论成功与否都继续测试
except Exception as e:
print(f"⚠️ 设置止盈止损失败: {e}")
return True # 不算失败,继续测试
def test_11_market_order_close(self):
"""测试 11: 市价平仓"""
print("\n🔒 测试 11: 市价平仓")
print("-" * 40)
try:
# 获取当前价格
ticker = self.api.exchange.fetch_ticker('BTC/USDT:USDT')
current_price = ticker['last']
print(f" 当前 BTC 价格: ${current_price:,.2f}")
# 先撤销止盈止损单
try:
self.api.cancel_all_orders('BTCUSDT')
print(" 已撤销止盈止损单")
except:
pass
# 使用 close_position 方法平仓
print(f" 下市价平仓单...")
order = self.api.close_position(
symbol='BTCUSDT',
side='sell', # 平多
size=0.01, # 0.01 张
)
if order:
order_id = order.get('id')
avg_price = order.get('average') or order.get('price') or 0
if avg_price:
avg_price = float(avg_price)
print(f"✅ 平仓成功: {order_id}")
print(f" 平仓均价: ${avg_price:,.2f}")
# 计算盈亏
if self.market_entry_price > 0 and avg_price > 0:
pnl = (avg_price - self.market_entry_price) * 0.01 # 1张 = 0.01 BTC
pnl_pct = (avg_price - self.market_entry_price) / self.market_entry_price * 100
print(f" 实现盈亏: ${pnl:,.2f} ({pnl_pct:+.2f}%)")
return True
else:
print("❌ 平仓返回空")
return False
except Exception as e:
print(f"❌ 市价平仓失败: {e}")
import traceback
traceback.print_exc()
return False
def test_12_verify_position_closed(self):
"""测试 12: 验证持仓已平"""
print("\n✅ 测试 12: 验证持仓已平")
print("-" * 40)
try:
positions = self.api.get_position('BTCUSDT')
has_position = False
if positions:
for pos in positions:
if float(pos.get('contracts', 0)) > 0:
has_position = True
print(f"⚠️ 仍有持仓: {pos.get('contracts')}")
if not has_position:
print("✅ 持仓已全部平仓")
return True
else:
print("❌ 持仓未完全平仓")
return False
except Exception as e:
print(f"❌ 验证平仓失败: {e}")
return False
# ==================== Service 层测试 ====================
def test_13_service_get_account_state(self):
"""测试 13: Service 层获取账户状态"""
print("\n🏦 测试 13: Service 获取账户状态")
print("-" * 40)
try:
state = self.service.get_account_state()
print(f"✅ 账户价值: ${state['account_value']:,.2f}")
print(f"✅ 已用保证金: ${state['total_margin_used']:,.2f}")
print(f"✅ 可用余额: ${state['available_balance']:,.2f}")
return True
except Exception as e:
print(f"❌ 获取账户状态失败: {e}")
import traceback
traceback.print_exc()
return False
def test_14_service_get_positions(self):
"""测试 14: Service 层获取持仓"""
print("\n📈 测试 14: Service 获取持仓")
print("-" * 40)
try:
positions = self.service.get_open_positions()
if positions:
print(f"✅ 当前持仓数: {len(positions)}")
for pos in positions:
# 安全获取字段
symbol = pos.get('symbol', 'N/A')
side = pos.get('side', 'N/A')
size = pos.get('size', 0)
entry = pos.get('entry_price', 0)
print(f" - {symbol}: {side} {size} @ ${entry:,.2f}")
else:
print("✅ 当前无持仓")
return True
except Exception as e:
print(f"❌ 获取持仓失败: {e}")
import traceback
traceback.print_exc()
return False
def test_15_service_contract_size(self):
"""测试 15: Service 合约面值"""
print("\n📐 测试 15: 合约面值换算")
print("-" * 40)
try:
# 测试已知币种
btc_size = self.service.get_contract_size('BTC')
eth_size = self.service.get_contract_size('ETH')
sol_size = self.service.get_contract_size('SOL')
print(f"✅ BTC 合约面值: {btc_size} BTC/张")
print(f"✅ ETH 合约面值: {eth_size} ETH/张")
print(f"✅ SOL 合约面值: {sol_size} SOL/张")
# 测试币数转合约数
contracts = self.service.coins_to_contracts('BTC', 0.05) # 0.05 BTC
print(f"✅ 0.05 BTC = {contracts}")
return True
except Exception as e:
print(f"❌ 合约面值测试失败: {e}")
return False
def test_16_risk_check(self):
"""测试 16: 风控检查"""
print("\n⚠️ 测试 16: 风控检查")
print("-" * 40)
try:
result = self.service.check_risk_limits()
if result['allowed']:
print(f"✅ 风控检查通过")
else:
print(f"⚠️ 风控检查未通过: {result['reason']}")
return True
except Exception as e:
print(f"⚠️ 风控检查异常: {e}")
return True # 不算失败
# ==================== 清理和运行 ====================
def cleanup(self):
"""清理测试产生的订单和持仓"""
print("\n🧹 清理测试订单...")
print("-" * 40)
try:
# 撤销所有 BTC 挂单
success = self.api.cancel_all_orders('BTCUSDT')
if success:
print("✅ 已撤销所有 BTC 挂单")
else:
print("⚠️ 撤销 BTC 挂单返回 False可能无挂单")
# 检查是否有残留持仓
positions = self.api.get_position('BTCUSDT')
if positions:
for pos in positions:
contracts = float(pos.get('contracts', 0))
if contracts > 0:
symbol = pos.get('symbol', '')
print(f"⚠️ 发现残留持仓: {symbol} {contracts}")
print(f" 请手动平仓!")
except Exception as e:
print(f"⚠️ 清理失败: {e}")
def run_all_tests(self):
"""运行所有测试"""
tests = [
self.test_01_connection,
self.test_02_get_balance,
self.test_03_get_positions,
self.test_04_set_leverage,
self.test_05_place_limit_order,
self.test_06_get_open_orders,
self.test_07_cancel_order,
self.test_08_market_order_open,
self.test_09_verify_position_opened,
self.test_10_set_tp_sl,
self.test_11_market_order_close,
self.test_12_verify_position_closed,
self.test_13_service_get_account_state,
self.test_14_service_get_positions,
self.test_15_service_contract_size,
self.test_16_risk_check,
]
results = []
for test in tests:
try:
result = test()
results.append((test.__name__, result))
time.sleep(0.5) # 避免触发频率限制
except Exception as e:
print(f"\n❌ 测试异常: {e}")
import traceback
traceback.print_exc()
results.append((test.__name__, False))
# 清理
self.cleanup()
# 汇总
print("\n" + "=" * 60)
print("测试结果汇总")
print("=" * 60)
passed = sum(1 for _, r in results if r)
total = len(results)
for name, result in results:
status = "✅ PASS" if result else "❌ FAIL"
print(f"{status}: {name}")
print(f"\n总计: {passed}/{total} 通过")
print("=" * 60)
return passed == total
if __name__ == '__main__':
print("\n" + "!" * 60)
print("⚠️ 警告:此测试将使用真实 API 调用!")
print("!" * 60)
print("请确认:")
print("1. 你已了解这会产生真实 API 调用")
print("2. 你使用的是测试网或接受小额手续费")
print("3. 市价单会立即成交,产生实际盈亏")
print("4. 测试后请检查是否有残留订单/持仓")
print("!" * 60)
confirm = input("\n是否继续?(yes/no): ")
if confirm.lower() != 'yes':
print("已取消测试")
sys.exit(0)
try:
tester = BitgetIntegrationTest()
success = tester.run_all_tests()
sys.exit(0 if success else 1)
except Exception as e:
print(f"\n❌ 测试失败: {e}")
import traceback
traceback.print_exc()
sys.exit(1)