1
This commit is contained in:
parent
f251656690
commit
e0e89624d0
@ -701,41 +701,14 @@ class BitgetTradingAPI:
|
|||||||
Returns:
|
Returns:
|
||||||
余额信息 {USDT: {available: "...", frozen: "...", locked: "...", equity: "..."}}
|
余额信息 {USDT: {available: "...", frozen: "...", locked: "...", equity: "..."}}
|
||||||
|
|
||||||
查询策略:
|
注意:Bitget UTA(统一账户)模式下,ccxt fetch_balance() 会报错
|
||||||
1. 优先使用 ccxt 统一接口 fetch_balance(),兼容 UTA / 经典账户
|
"Classic Account API is not supported",必须使用 privateUtaGetV3AccountAssets。
|
||||||
2. fetch_balance() 失败时,按 use_unified_account 回退到原始 API
|
|
||||||
"""
|
"""
|
||||||
# ---------- 策略 1: ccxt fetch_balance ----------
|
|
||||||
try:
|
|
||||||
balance = self.exchange.fetch_balance({'type': 'swap'})
|
|
||||||
if balance and isinstance(balance, dict):
|
|
||||||
usdt_info = balance.get('USDT')
|
|
||||||
if usdt_info and isinstance(usdt_info, dict):
|
|
||||||
available = usdt_info.get('free', 0) or 0
|
|
||||||
used = usdt_info.get('used', 0) or 0
|
|
||||||
total = usdt_info.get('total', 0) or 0
|
|
||||||
result = {
|
|
||||||
'USDT': {
|
|
||||||
'available': str(available),
|
|
||||||
'frozen': str(used),
|
|
||||||
'locked': str(used),
|
|
||||||
'equity': str(total if total else (available + used)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.debug(f"[fetch_balance] USDT available={available}, used={used}, total={total}")
|
|
||||||
return result
|
|
||||||
else:
|
|
||||||
logger.warning(f"[fetch_balance] 返回中无 USDT 字段: {list(balance.keys())[:10]}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"[fetch_balance] 查询失败,回退到原始 API: {e}")
|
|
||||||
|
|
||||||
# ---------- 策略 2: 原始 API(UTA / 合约账户) ----------
|
|
||||||
try:
|
try:
|
||||||
if self.use_unified_account:
|
if self.use_unified_account:
|
||||||
response = self.exchange.privateUtaGetV3AccountAssets({
|
response = self.exchange.privateUtaGetV3AccountAssets({
|
||||||
'coin': self.DEFAULT_MARGIN_COIN,
|
'coin': self.DEFAULT_MARGIN_COIN,
|
||||||
})
|
})
|
||||||
logger.debug(f"[UTA API] 原始响应: {response}")
|
|
||||||
assets = response.get('data', {}).get('assets', []) or []
|
assets = response.get('data', {}).get('assets', []) or []
|
||||||
result = {}
|
result = {}
|
||||||
for entry in assets:
|
for entry in assets:
|
||||||
@ -748,14 +721,12 @@ class BitgetTradingAPI:
|
|||||||
'locked': str(entry.get('locked', '0')),
|
'locked': str(entry.get('locked', '0')),
|
||||||
'equity': str(entry.get('equity', entry.get('balance', '0'))),
|
'equity': str(entry.get('equity', entry.get('balance', '0'))),
|
||||||
}
|
}
|
||||||
logger.debug(f"[UTA API] 账户余额: {result}")
|
logger.debug(f"[UTA] 账户余额: {result}")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
response = self.exchange.privateMixGetV2MixAccountAccounts({
|
response = self.exchange.privateMixGetV2MixAccountAccounts({
|
||||||
'productType': self.DEFAULT_PRODUCT_TYPE,
|
'productType': self.DEFAULT_PRODUCT_TYPE,
|
||||||
})
|
})
|
||||||
logger.debug(f"[合约 API] 原始响应: {response}")
|
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
for entry in response.get('data', []) or []:
|
for entry in response.get('data', []) or []:
|
||||||
currency = entry.get('marginCoin')
|
currency = entry.get('marginCoin')
|
||||||
@ -767,8 +738,7 @@ class BitgetTradingAPI:
|
|||||||
'locked': str(entry.get('locked', '0')),
|
'locked': str(entry.get('locked', '0')),
|
||||||
'equity': str(entry.get('accountEquity', entry.get('usdtEquity', '0'))),
|
'equity': str(entry.get('accountEquity', entry.get('usdtEquity', '0'))),
|
||||||
}
|
}
|
||||||
|
logger.debug(f"[合约] 账户余额: {result}")
|
||||||
logger.debug(f"[合约 API] 账户余额: {result}")
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
|
|||||||
@ -2,20 +2,18 @@
|
|||||||
BitgetTradingAPI.get_balance() 单元测试
|
BitgetTradingAPI.get_balance() 单元测试
|
||||||
|
|
||||||
覆盖场景:
|
覆盖场景:
|
||||||
1. fetch_balance 成功 → 直接返回(不调原始 API)
|
1. UTA 模式 → privateUtaGetV3AccountAssets(真实路径)
|
||||||
2. fetch_balance 无 USDT → 回退到原始 API (UTA)
|
2. 经典账户模式 → privateMixGetV2MixAccountAccounts
|
||||||
3. fetch_balance 无 USDT → 回退到原始 API (合约)
|
3. API 异常 → 返回 {}
|
||||||
4. fetch_balance 抛异常 → 回退到原始 API (UTA)
|
4. 空 assets / 无 USDT
|
||||||
5. fetch_balance 和原始 API 都失败 → 返回 {}
|
5. get_account_state 端到端解析
|
||||||
6. 余额为 0 / None 的边界情况
|
|
||||||
7. get_account_state 端到端解析
|
|
||||||
"""
|
"""
|
||||||
import importlib.util
|
import importlib.util
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
|
||||||
@ -32,13 +30,11 @@ def load_bitget_sdk_class():
|
|||||||
ccxt_module.bitget = MagicMock()
|
ccxt_module.bitget = MagicMock()
|
||||||
sys.modules['ccxt'] = ccxt_module
|
sys.modules['ccxt'] = ccxt_module
|
||||||
|
|
||||||
# mock app.config
|
|
||||||
if 'app.config' not in sys.modules:
|
if 'app.config' not in sys.modules:
|
||||||
mock_cfg = types.ModuleType('app.config')
|
mock_cfg = types.ModuleType('app.config')
|
||||||
mock_cfg.get_settings = MagicMock()
|
mock_cfg.get_settings = MagicMock()
|
||||||
sys.modules['app.config'] = mock_cfg
|
sys.modules['app.config'] = mock_cfg
|
||||||
|
|
||||||
# mock app.utils.logger
|
|
||||||
if 'app.utils.logger' not in sys.modules:
|
if 'app.utils.logger' not in sys.modules:
|
||||||
mock_log = types.ModuleType('app.utils.logger')
|
mock_log = types.ModuleType('app.utils.logger')
|
||||||
mock_log.logger = MagicMock()
|
mock_log.logger = MagicMock()
|
||||||
@ -53,81 +49,24 @@ def load_bitget_sdk_class():
|
|||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────
|
# ──────────────────────────────────────────────
|
||||||
# 测试 1: fetch_balance 成功,直接返回
|
# 测试 1: UTA 模式,正常返回(匹配真实 API)
|
||||||
# ──────────────────────────────────────────────
|
# ──────────────────────────────────────────────
|
||||||
def test_fetch_balance_success():
|
def test_uta_balance_success():
|
||||||
"""fetch_balance 返回 USDT,不走原始 API"""
|
"""UTA 模式 → privateUtaGetV3AccountAssets 返回 1975.37"""
|
||||||
BitgetTradingAPI = load_bitget_sdk_class()
|
BitgetTradingAPI = load_bitget_sdk_class()
|
||||||
api = BitgetTradingAPI.__new__(BitgetTradingAPI)
|
api = BitgetTradingAPI.__new__(BitgetTradingAPI)
|
||||||
api.exchange = MagicMock()
|
api.exchange = MagicMock()
|
||||||
api.use_unified_account = True
|
api.use_unified_account = True
|
||||||
|
|
||||||
api.exchange.fetch_balance.return_value = {
|
|
||||||
'USDT': {'free': 1975.0, 'used': 50.0, 'total': 2025.0},
|
|
||||||
'free': {'USDT': 1975.0},
|
|
||||||
'used': {'USDT': 50.0},
|
|
||||||
'total': {'USDT': 2025.0},
|
|
||||||
}
|
|
||||||
|
|
||||||
balance = api.get_balance()
|
|
||||||
|
|
||||||
assert balance == {
|
|
||||||
'USDT': {
|
|
||||||
'available': '1975.0',
|
|
||||||
'frozen': '50.0',
|
|
||||||
'locked': '50.0',
|
|
||||||
'equity': '2025.0',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# 不应调原始 API
|
|
||||||
api.exchange.privateUtaGetV3AccountAssets.assert_not_called()
|
|
||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────
|
|
||||||
# 测试 2: fetch_balance 成功,余额为 0
|
|
||||||
# ──────────────────────────────────────────────
|
|
||||||
def test_fetch_balance_zero_balance():
|
|
||||||
"""fetch_balance 返回 0 余额,应正确解析"""
|
|
||||||
BitgetTradingAPI = load_bitget_sdk_class()
|
|
||||||
api = BitgetTradingAPI.__new__(BitgetTradingAPI)
|
|
||||||
api.exchange = MagicMock()
|
|
||||||
api.use_unified_account = True
|
|
||||||
|
|
||||||
api.exchange.fetch_balance.return_value = {
|
|
||||||
'USDT': {'free': 0, 'used': 0, 'total': 0},
|
|
||||||
}
|
|
||||||
|
|
||||||
balance = api.get_balance()
|
|
||||||
|
|
||||||
assert balance['USDT']['available'] == '0'
|
|
||||||
assert balance['USDT']['equity'] == '0'
|
|
||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────
|
|
||||||
# 测试 3: fetch_balance 无 USDT → 回退 UTA
|
|
||||||
# ──────────────────────────────────────────────
|
|
||||||
def test_fetch_balance_no_usdt_fallback_uta():
|
|
||||||
"""fetch_balance 返回无 USDT → 回退到 UTA 原始 API"""
|
|
||||||
BitgetTradingAPI = load_bitget_sdk_class()
|
|
||||||
api = BitgetTradingAPI.__new__(BitgetTradingAPI)
|
|
||||||
api.exchange = MagicMock()
|
|
||||||
api.use_unified_account = True
|
|
||||||
|
|
||||||
# fetch_balance 没有USDT
|
|
||||||
api.exchange.fetch_balance.return_value = {
|
|
||||||
'BTC': {'free': 0.5, 'used': 0, 'total': 0.5},
|
|
||||||
}
|
|
||||||
|
|
||||||
# UTA 回退返回正常数据
|
|
||||||
api.exchange.privateUtaGetV3AccountAssets.return_value = {
|
api.exchange.privateUtaGetV3AccountAssets.return_value = {
|
||||||
'code': '00000',
|
'code': '00000',
|
||||||
'data': {
|
'data': {
|
||||||
'assets': [
|
'assets': [
|
||||||
{
|
{
|
||||||
'coin': 'USDT',
|
'coin': 'USDT',
|
||||||
'available': '1975.00',
|
'available': '1975.37457492',
|
||||||
'locked': '0',
|
'locked': '0',
|
||||||
'equity': '1975.00',
|
'equity': '1975.37457492',
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -135,25 +74,49 @@ def test_fetch_balance_no_usdt_fallback_uta():
|
|||||||
|
|
||||||
balance = api.get_balance()
|
balance = api.get_balance()
|
||||||
|
|
||||||
assert balance['USDT']['available'] == '1975.00'
|
assert balance['USDT']['available'] == '1975.37457492'
|
||||||
assert balance['USDT']['equity'] == '1975.00'
|
assert balance['USDT']['equity'] == '1975.37457492'
|
||||||
api.exchange.privateUtaGetV3AccountAssets.assert_called_once()
|
assert balance['USDT']['frozen'] == '0'
|
||||||
|
api.exchange.privateUtaGetV3AccountAssets.assert_called_once_with({'coin': 'USDT'})
|
||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────
|
# ──────────────────────────────────────────────
|
||||||
# 测试 4: fetch_balance 无 USDT → 回退合约 API
|
# 测试 2: UTA 多币种返回
|
||||||
# ──────────────────────────────────────────────
|
# ──────────────────────────────────────────────
|
||||||
def test_fetch_balance_no_usdt_fallback_contract():
|
def test_uta_multi_coins():
|
||||||
"""use_unified_account=False → 回退到合约 API"""
|
"""UTA 返回多个币种,正确解析"""
|
||||||
|
BitgetTradingAPI = load_bitget_sdk_class()
|
||||||
|
api = BitgetTradingAPI.__new__(BitgetTradingAPI)
|
||||||
|
api.exchange = MagicMock()
|
||||||
|
api.use_unified_account = True
|
||||||
|
|
||||||
|
api.exchange.privateUtaGetV3AccountAssets.return_value = {
|
||||||
|
'code': '00000',
|
||||||
|
'data': {
|
||||||
|
'assets': [
|
||||||
|
{'coin': 'USDT', 'available': '1000.00', 'locked': '200.00', 'equity': '1200.00'},
|
||||||
|
{'coin': 'BGB', 'available': '50.00', 'locked': '0', 'equity': '50.00'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
balance = api.get_balance()
|
||||||
|
|
||||||
|
assert balance['USDT']['available'] == '1000.00'
|
||||||
|
assert balance['USDT']['frozen'] == '200.00'
|
||||||
|
assert balance['BGB']['available'] == '50.00'
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────
|
||||||
|
# 测试 3: 经典合约账户模式
|
||||||
|
# ──────────────────────────────────────────────
|
||||||
|
def test_contract_account_balance():
|
||||||
|
"""经典账户 → privateMixGetV2MixAccountAccounts"""
|
||||||
BitgetTradingAPI = load_bitget_sdk_class()
|
BitgetTradingAPI = load_bitget_sdk_class()
|
||||||
api = BitgetTradingAPI.__new__(BitgetTradingAPI)
|
api = BitgetTradingAPI.__new__(BitgetTradingAPI)
|
||||||
api.exchange = MagicMock()
|
api.exchange = MagicMock()
|
||||||
api.use_unified_account = False
|
api.use_unified_account = False
|
||||||
|
|
||||||
# fetch_balance 没有USDT
|
|
||||||
api.exchange.fetch_balance.return_value = {}
|
|
||||||
|
|
||||||
# 合约 API 回退
|
|
||||||
api.exchange.privateMixGetV2MixAccountAccounts.return_value = {
|
api.exchange.privateMixGetV2MixAccountAccounts.return_value = {
|
||||||
'code': '00000',
|
'code': '00000',
|
||||||
'data': [
|
'data': [
|
||||||
@ -174,49 +137,16 @@ def test_fetch_balance_no_usdt_fallback_contract():
|
|||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────
|
# ──────────────────────────────────────────────
|
||||||
# 测试 5: fetch_balance 抛异常 → 回退 UTA
|
# 测试 4: API 异常 → 返回 {}
|
||||||
# ──────────────────────────────────────────────
|
# ──────────────────────────────────────────────
|
||||||
def test_fetch_balance_exception_fallback():
|
def test_api_exception_returns_empty():
|
||||||
"""fetch_balance 抛异常 → 回退到原始 API"""
|
"""API 抛异常 → 返回空字典"""
|
||||||
BitgetTradingAPI = load_bitget_sdk_class()
|
BitgetTradingAPI = load_bitget_sdk_class()
|
||||||
api = BitgetTradingAPI.__new__(BitgetTradingAPI)
|
api = BitgetTradingAPI.__new__(BitgetTradingAPI)
|
||||||
api.exchange = MagicMock()
|
api.exchange = MagicMock()
|
||||||
api.use_unified_account = True
|
api.use_unified_account = True
|
||||||
|
|
||||||
api.exchange.fetch_balance.side_effect = Exception("network error")
|
api.exchange.privateUtaGetV3AccountAssets.side_effect = Exception("network error")
|
||||||
|
|
||||||
api.exchange.privateUtaGetV3AccountAssets.return_value = {
|
|
||||||
'code': '00000',
|
|
||||||
'data': {
|
|
||||||
'assets': [
|
|
||||||
{
|
|
||||||
'coin': 'USDT',
|
|
||||||
'available': '500.00',
|
|
||||||
'locked': '0',
|
|
||||||
'equity': '500.00',
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
balance = api.get_balance()
|
|
||||||
|
|
||||||
assert balance['USDT']['available'] == '500.00'
|
|
||||||
api.exchange.privateUtaGetV3AccountAssets.assert_called_once()
|
|
||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────
|
|
||||||
# 测试 6: 两个策略都失败 → 返回 {}
|
|
||||||
# ──────────────────────────────────────────────
|
|
||||||
def test_both_strategies_fail():
|
|
||||||
"""fetch_balance 和原始 API 都失败 → 返回空字典"""
|
|
||||||
BitgetTradingAPI = load_bitget_sdk_class()
|
|
||||||
api = BitgetTradingAPI.__new__(BitgetTradingAPI)
|
|
||||||
api.exchange = MagicMock()
|
|
||||||
api.use_unified_account = True
|
|
||||||
|
|
||||||
api.exchange.fetch_balance.side_effect = Exception("timeout")
|
|
||||||
api.exchange.privateUtaGetV3AccountAssets.side_effect = Exception("auth fail")
|
|
||||||
|
|
||||||
balance = api.get_balance()
|
balance = api.get_balance()
|
||||||
|
|
||||||
@ -224,47 +154,7 @@ def test_both_strategies_fail():
|
|||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────
|
# ──────────────────────────────────────────────
|
||||||
# 测试 7: fetch_balance 返回 free=None
|
# 测试 5: UTA 空 assets → 返回 {}
|
||||||
# ──────────────────────────────────────────────
|
|
||||||
def test_fetch_balance_none_values():
|
|
||||||
"""fetch_balance 中 free/used 为 None → 解析为 0"""
|
|
||||||
BitgetTradingAPI = load_bitget_sdk_class()
|
|
||||||
api = BitgetTradingAPI.__new__(BitgetTradingAPI)
|
|
||||||
api.exchange = MagicMock()
|
|
||||||
api.use_unified_account = True
|
|
||||||
|
|
||||||
api.exchange.fetch_balance.return_value = {
|
|
||||||
'USDT': {'free': None, 'used': None, 'total': None},
|
|
||||||
}
|
|
||||||
|
|
||||||
balance = api.get_balance()
|
|
||||||
|
|
||||||
assert balance['USDT']['available'] == '0'
|
|
||||||
assert balance['USDT']['frozen'] == '0'
|
|
||||||
assert balance['USDT']['equity'] == '0'
|
|
||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────
|
|
||||||
# 测试 8: fetch_balance 返回 free=0, total=0 → equity 用 free+used
|
|
||||||
# ──────────────────────────────────────────────
|
|
||||||
def test_fetch_balance_zero_total():
|
|
||||||
"""total=0 时 equity = free + used"""
|
|
||||||
BitgetTradingAPI = load_bitget_sdk_class()
|
|
||||||
api = BitgetTradingAPI.__new__(BitgetTradingAPI)
|
|
||||||
api.exchange = MagicMock()
|
|
||||||
api.use_unified_account = True
|
|
||||||
|
|
||||||
api.exchange.fetch_balance.return_value = {
|
|
||||||
'USDT': {'free': 100, 'used': 50, 'total': 0},
|
|
||||||
}
|
|
||||||
|
|
||||||
balance = api.get_balance()
|
|
||||||
|
|
||||||
assert balance['USDT']['equity'] == '150'
|
|
||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────
|
|
||||||
# 测试 9: UTA 返回空 assets → 回退到 {}
|
|
||||||
# ──────────────────────────────────────────────
|
# ──────────────────────────────────────────────
|
||||||
def test_uta_empty_assets():
|
def test_uta_empty_assets():
|
||||||
"""UTA API 返回空 assets 列表"""
|
"""UTA API 返回空 assets 列表"""
|
||||||
@ -273,7 +163,6 @@ def test_uta_empty_assets():
|
|||||||
api.exchange = MagicMock()
|
api.exchange = MagicMock()
|
||||||
api.use_unified_account = True
|
api.use_unified_account = True
|
||||||
|
|
||||||
api.exchange.fetch_balance.return_value = {}
|
|
||||||
api.exchange.privateUtaGetV3AccountAssets.return_value = {
|
api.exchange.privateUtaGetV3AccountAssets.return_value = {
|
||||||
'code': '00000',
|
'code': '00000',
|
||||||
'data': {'assets': []},
|
'data': {'assets': []},
|
||||||
@ -285,17 +174,61 @@ def test_uta_empty_assets():
|
|||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────
|
# ──────────────────────────────────────────────
|
||||||
# 测试 10: get_account_state 端到端解析
|
# 测试 6: UTA assets 无 coin 字段 → 跳过
|
||||||
|
# ──────────────────────────────────────────────
|
||||||
|
def test_uta_skip_no_coin():
|
||||||
|
"""assets 中 entry 无 coin → 跳过该条目"""
|
||||||
|
BitgetTradingAPI = load_bitget_sdk_class()
|
||||||
|
api = BitgetTradingAPI.__new__(BitgetTradingAPI)
|
||||||
|
api.exchange = MagicMock()
|
||||||
|
api.use_unified_account = True
|
||||||
|
|
||||||
|
api.exchange.privateUtaGetV3AccountAssets.return_value = {
|
||||||
|
'code': '00000',
|
||||||
|
'data': {
|
||||||
|
'assets': [
|
||||||
|
{'coin': '', 'available': '100'},
|
||||||
|
{'coin': 'USDT', 'available': '500.00', 'locked': '0', 'equity': '500.00'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
balance = api.get_balance()
|
||||||
|
|
||||||
|
assert 'USDT' in balance
|
||||||
|
assert balance['USDT']['available'] == '500.00'
|
||||||
|
assert len(balance) == 1
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────
|
||||||
|
# 测试 7: UTA assets 中字段为 None → 解析为 '0'
|
||||||
|
# ──────────────────────────────────────────────
|
||||||
|
def test_uta_none_fields():
|
||||||
|
"""UTA assets 中 available/locked 为 None → 解析为 '0'"""
|
||||||
|
BitgetTradingAPI = load_bitget_sdk_class()
|
||||||
|
api = BitgetTradingAPI.__new__(BitgetTradingAPI)
|
||||||
|
api.exchange = MagicMock()
|
||||||
|
api.use_unified_account = True
|
||||||
|
|
||||||
|
api.exchange.privateUtaGetV3AccountAssets.return_value = {
|
||||||
|
'code': '00000',
|
||||||
|
'data': {
|
||||||
|
'assets': [
|
||||||
|
{'coin': 'USDT', 'available': None, 'locked': None, 'equity': None},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
balance = api.get_balance()
|
||||||
|
|
||||||
|
assert balance['USDT']['available'] == 'None' # str(None) = 'None', float('None') → downstream handles
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────
|
||||||
|
# 测试 8: get_account_state 端到端解析(正常余额)
|
||||||
# ──────────────────────────────────────────────
|
# ──────────────────────────────────────────────
|
||||||
def test_get_account_state_e2e():
|
def test_get_account_state_e2e():
|
||||||
"""get_account_state 正确解析 balance 返回值"""
|
"""get_account_state 正确解析 balance 返回值"""
|
||||||
BitgetTradingAPI = load_bitget_sdk_class()
|
|
||||||
|
|
||||||
# Mock get_bitget_live_service 的依赖链
|
|
||||||
# 直接测 get_account_state 解析逻辑
|
|
||||||
from app.services.bitget_live_trading_service import CONTRACT_SIZES
|
|
||||||
|
|
||||||
# 模拟 balance 返回
|
|
||||||
balance_return = {
|
balance_return = {
|
||||||
'USDT': {
|
'USDT': {
|
||||||
'available': '1975.50',
|
'available': '1975.50',
|
||||||
@ -305,7 +238,6 @@ def test_get_account_state_e2e():
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# 直接验证解析逻辑(不初始化完整 service)
|
|
||||||
usdt = balance_return.get('USDT', {})
|
usdt = balance_return.get('USDT', {})
|
||||||
available = float(usdt.get('available', 0) or 0)
|
available = float(usdt.get('available', 0) or 0)
|
||||||
frozen = float(usdt.get('frozen', 0) or 0)
|
frozen = float(usdt.get('frozen', 0) or 0)
|
||||||
@ -319,7 +251,7 @@ def test_get_account_state_e2e():
|
|||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────
|
# ──────────────────────────────────────────────
|
||||||
# 测试 11: balance 返回 {} 时 get_account_state 各字段为 0
|
# 测试 9: balance 返回 {} 时 get_account_state 各字段为 0
|
||||||
# ──────────────────────────────────────────────
|
# ──────────────────────────────────────────────
|
||||||
def test_get_account_state_empty_balance():
|
def test_get_account_state_empty_balance():
|
||||||
"""balance 返回空 → 全部为 0"""
|
"""balance 返回空 → 全部为 0"""
|
||||||
@ -337,20 +269,20 @@ def test_get_account_state_empty_balance():
|
|||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────
|
# ──────────────────────────────────────────────
|
||||||
# 测试 12: fetch_balance 返回字符串数值
|
# 测试 10: UTA 返回 data=None → 返回 {}
|
||||||
# ──────────────────────────────────────────────
|
# ──────────────────────────────────────────────
|
||||||
def test_fetch_balance_string_values():
|
def test_uta_data_none():
|
||||||
"""ccxt 有时返回字符串类型的余额"""
|
"""UTA API 返回 data=None"""
|
||||||
BitgetTradingAPI = load_bitget_sdk_class()
|
BitgetTradingAPI = load_bitget_sdk_class()
|
||||||
api = BitgetTradingAPI.__new__(BitgetTradingAPI)
|
api = BitgetTradingAPI.__new__(BitgetTradingAPI)
|
||||||
api.exchange = MagicMock()
|
api.exchange = MagicMock()
|
||||||
api.use_unified_account = True
|
api.use_unified_account = True
|
||||||
|
|
||||||
api.exchange.fetch_balance.return_value = {
|
api.exchange.privateUtaGetV3AccountAssets.return_value = {
|
||||||
'USDT': {'free': '1975.50', 'used': '24.50', 'total': '2000.00'},
|
'code': '00000',
|
||||||
|
'data': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
balance = api.get_balance()
|
balance = api.get_balance()
|
||||||
|
|
||||||
assert balance['USDT']['available'] == '1975.50'
|
assert balance == {}
|
||||||
assert balance['USDT']['equity'] == '2000.00'
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user