auto_trade_sys/backend/api/routes/config.py
薇薇安 26b2674c86 a
2026-01-17 19:35:27 +08:00

395 lines
18 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.

"""
配置管理API
"""
from fastapi import APIRouter, HTTPException
from api.models.config import ConfigItem, ConfigUpdate
import sys
from pathlib import Path
import logging
# 添加项目根目录到路径
project_root = Path(__file__).parent.parent.parent.parent
sys.path.insert(0, str(project_root))
sys.path.insert(0, str(project_root / 'backend'))
sys.path.insert(0, str(project_root / 'trading_system'))
from database.models import TradingConfig
logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("")
@router.get("/")
async def get_all_configs():
"""获取所有配置"""
try:
configs = TradingConfig.get_all()
result = {}
for config in configs:
result[config['config_key']] = {
'value': TradingConfig._convert_value(
config['config_value'],
config['config_type']
),
'type': config['config_type'],
'category': config['category'],
'description': config['description']
}
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/feasibility-check")
async def check_config_feasibility():
"""
检查配置可行性,基于当前账户余额和杠杆倍数计算可行的配置建议
"""
try:
# 获取账户余额
try:
from api.routes.account import get_realtime_account_data
account_data = await get_realtime_account_data()
available_balance = account_data.get('available_balance', 0)
total_balance = account_data.get('total_balance', 0)
except Exception as e:
logger.warning(f"获取账户余额失败: {e},使用默认值")
available_balance = 0
total_balance = 0
if available_balance <= 0:
return {
"feasible": False,
"error": "无法获取账户余额请检查API配置",
"suggestions": []
}
# 获取当前配置
min_margin_usdt = TradingConfig.get_value('MIN_MARGIN_USDT', 5.0)
min_position_percent = TradingConfig.get_value('MIN_POSITION_PERCENT', 0.02)
max_position_percent = TradingConfig.get_value('MAX_POSITION_PERCENT', 0.08)
base_leverage = TradingConfig.get_value('LEVERAGE', 10)
max_leverage = TradingConfig.get_value('MAX_LEVERAGE', 15)
use_dynamic_leverage = TradingConfig.get_value('USE_DYNAMIC_LEVERAGE', True)
# 检查所有可能的杠杆倍数(考虑动态杠杆)
leverage_to_check = [base_leverage]
if use_dynamic_leverage and max_leverage > base_leverage:
# 检查基础杠杆、最大杠杆以及中间的关键值如15x, 20x, 30x, 40x
leverage_to_check.append(max_leverage)
# 添加常见的杠杆倍数
for lev in [15, 20, 25, 30, 40, 50]:
if base_leverage < lev <= max_leverage and lev not in leverage_to_check:
leverage_to_check.append(lev)
leverage_to_check = sorted(set(leverage_to_check))
# 检查每个杠杆倍数下的可行性
leverage_results = []
all_feasible = True
worst_case_leverage = None
worst_case_margin = None
for leverage in leverage_to_check:
# 计算最小保证金要求对应的最小仓位价值
required_position_value = min_margin_usdt * leverage
required_position_percent = required_position_value / available_balance if available_balance > 0 else 0
# 计算使用最小仓位百分比时,实际能得到的保证金
min_position_value = available_balance * min_position_percent
actual_min_margin = min_position_value / leverage if leverage > 0 else min_position_value
# 检查是否可行:
# 1. 需要的仓位百分比不能超过最大允许的仓位百分比
# 2. 使用最小仓位百分比时,实际保证金必须 >= 最小保证金要求
condition1_ok = required_position_percent <= max_position_percent
condition2_ok = actual_min_margin >= min_margin_usdt
is_feasible_at_leverage = condition1_ok and condition2_ok
leverage_results.append({
'leverage': leverage,
'required_position_value': required_position_value,
'required_position_percent': required_position_percent * 100,
'actual_min_margin': actual_min_margin,
'feasible': is_feasible_at_leverage,
'condition1_ok': condition1_ok,
'condition2_ok': condition2_ok
})
if not is_feasible_at_leverage:
all_feasible = False
# 记录最坏情况(实际保证金最小的)
if worst_case_margin is None or actual_min_margin < worst_case_margin:
worst_case_leverage = leverage
worst_case_margin = actual_min_margin
# 使用基础杠杆的结果作为主要判断
base_result = next((r for r in leverage_results if r['leverage'] == base_leverage), leverage_results[0])
is_feasible = all_feasible
suggestions = []
if not is_feasible:
# 不可行,给出建议
# 找出不可行的杠杆倍数
infeasible_leverages = [r for r in leverage_results if not r['feasible']]
if infeasible_leverages:
# 找出最坏情况(实际保证金最小的)
worst = min(infeasible_leverages, key=lambda x: x['actual_min_margin'])
worst_leverage = worst['leverage']
worst_margin = worst['actual_min_margin']
# 方案1基于最坏情况最大杠杆降低最小保证金
if not worst['condition2_ok']:
suggestions.append({
"type": "reduce_min_margin_to_supported",
"title": f"降低最小保证金(考虑{worst_leverage}x杠杆",
"description": f"将 MIN_MARGIN_USDT 调整为 {worst_margin:.2f} USDT当前: {min_margin_usdt:.2f} USDT。在{worst_leverage}x杠杆下实际只支持 {worst_margin:.2f} USDT",
"config_key": "MIN_MARGIN_USDT",
"suggested_value": round(worst_margin, 2),
"reason": f"{worst_leverage}x杠杆下使用最小仓位 {min_position_percent*100:.1f}% 时,实际保证金只有 {worst_margin:.2f} USDT无法满足 {min_margin_usdt:.2f} USDT 的要求"
})
# 方案2基于最大杠杆计算需要的最小保证金
max_leverage_result = next((r for r in leverage_results if r['leverage'] == max_leverage), None)
if max_leverage_result and not max_leverage_result['feasible']:
if max_leverage_result['required_position_percent'] > max_position_percent:
suggested_min_margin_max_lev = (available_balance * max_position_percent) / max_leverage
suggestions.append({
"type": "reduce_min_margin_for_max_leverage",
"title": f"降低最小保证金(支持{max_leverage}x杠杆",
"description": f"将 MIN_MARGIN_USDT 调整为 {suggested_min_margin_max_lev:.2f} USDT当前: {min_margin_usdt:.2f} USDT以支持{max_leverage}x杠杆",
"config_key": "MIN_MARGIN_USDT",
"suggested_value": round(suggested_min_margin_max_lev, 2),
"reason": f"{max_leverage}x杠杆下需要 {max_leverage_result['required_position_percent']:.1f}% 的仓位价值,但最大允许 {max_position_percent*100:.1f}%"
})
# 方案3降低最大杠杆
if use_dynamic_leverage:
# 计算能支持的最大杠杆
max_supported_leverage = int((available_balance * max_position_percent) / min_margin_usdt)
if max_supported_leverage < max_leverage and max_supported_leverage >= base_leverage:
suggestions.append({
"type": "reduce_max_leverage",
"title": "降低最大杠杆倍数",
"description": f"将 MAX_LEVERAGE 调整为 {max_supported_leverage}x当前: {max_leverage}x以支持当前配置",
"config_key": "MAX_LEVERAGE",
"suggested_value": max_supported_leverage,
"reason": f"当前配置下,最大只能支持 {max_supported_leverage}x 杠杆才能满足最小保证金要求"
})
# 方案4增加账户余额
if worst['required_position_percent'] > max_position_percent:
required_balance = worst['required_position_value'] / max_position_percent
suggestions.append({
"type": "increase_balance",
"title": "增加账户余额",
"description": f"将账户余额增加到至少 {required_balance:.2f} USDT当前: {available_balance:.2f} USDT以支持{worst_leverage}x杠杆",
"config_key": None,
"suggested_value": round(required_balance, 2),
"reason": f"{worst_leverage}x杠杆下当前余额不足以满足最小保证金 {min_margin_usdt:.2f} USDT 的要求"
})
# 显示所有杠杆倍数的检查结果
suggestions.append({
"type": "leverage_analysis",
"title": "各杠杆倍数检查结果",
"description": "详细检查结果见下方",
"config_key": None,
"suggested_value": None,
"reason": None,
"leverage_results": leverage_results
})
else:
# 可行,显示当前配置信息
actual_min_position_value = available_balance * min_position_percent
actual_min_margin = base_result['actual_min_margin']
suggestions.append({
"type": "info",
"title": "配置可行",
"description": f"当前配置可以正常下单。最小仓位价值: {actual_min_position_value:.2f} USDT对应保证金: {actual_min_margin:.2f} USDT{base_leverage}x杠杆",
"config_key": None,
"suggested_value": None,
"reason": None
})
# 如果启用了动态杠杆,显示所有杠杆倍数的检查结果
if use_dynamic_leverage and len(leverage_to_check) > 1:
suggestions.append({
"type": "leverage_analysis",
"title": "各杠杆倍数检查结果(全部可行)",
"description": "详细检查结果见下方",
"config_key": None,
"suggested_value": None,
"reason": None,
"leverage_results": leverage_results
})
return {
"feasible": is_feasible,
"account_balance": available_balance,
"base_leverage": base_leverage,
"max_leverage": max_leverage,
"use_dynamic_leverage": use_dynamic_leverage,
"current_config": {
"min_margin_usdt": min_margin_usdt,
"min_position_percent": min_position_percent,
"max_position_percent": max_position_percent
},
"calculated_values": {
"required_position_value": base_result['required_position_value'],
"required_position_percent": base_result['required_position_percent'],
"max_allowed_position_percent": max_position_percent * 100,
"min_position_value": available_balance * min_position_percent,
"actual_min_margin": base_result['actual_min_margin'],
"min_position_percent": min_position_percent * 100
},
"leverage_analysis": {
"leverages_checked": leverage_to_check,
"all_feasible": all_feasible,
"results": leverage_results
},
"suggestions": suggestions
}
except Exception as e:
logger.error(f"检查配置可行性失败: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=f"检查配置可行性失败: {str(e)}")
@router.get("/{key}")
async def get_config(key: str):
"""获取单个配置"""
try:
config = TradingConfig.get(key)
if not config:
raise HTTPException(status_code=404, detail="Config not found")
return {
'key': config['config_key'],
'value': TradingConfig._convert_value(
config['config_value'],
config['config_type']
),
'type': config['config_type'],
'category': config['category'],
'description': config['description']
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/{key}")
async def update_config(key: str, item: ConfigUpdate):
"""更新配置"""
try:
# 获取现有配置以确定类型和分类
existing = TradingConfig.get(key)
if not existing:
raise HTTPException(status_code=404, detail="Config not found")
config_type = item.type or existing['config_type']
category = item.category or existing['category']
description = item.description or existing['description']
# 验证配置值
if config_type == 'number':
try:
float(item.value)
except (ValueError, TypeError):
raise HTTPException(status_code=400, detail=f"Invalid number value for {key}")
elif config_type == 'boolean':
if not isinstance(item.value, bool):
# 尝试转换
if isinstance(item.value, str):
item.value = item.value.lower() in ('true', '1', 'yes', 'on')
else:
item.value = bool(item.value)
# 特殊验证百分比配置应该在0-1之间
if 'PERCENT' in key and config_type == 'number':
if not (0 <= float(item.value) <= 1):
raise HTTPException(
status_code=400,
detail=f"{key} must be between 0 and 1 (0% to 100%)"
)
# 更新配置
TradingConfig.set(key, item.value, config_type, category, description)
# 清除配置缓存,确保立即生效
try:
from config_manager import ConfigManager
# 如果存在全局配置管理器实例,清除其缓存
import config_manager
if hasattr(config_manager, '_config_manager') and config_manager._config_manager:
config_manager._config_manager.reload()
except Exception as e:
logger.debug(f"清除配置缓存失败: {e}")
return {
"message": "配置已更新",
"key": key,
"value": item.value,
"note": "交易系统将在下次扫描时自动使用新配置"
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/batch")
async def update_configs_batch(configs: list[ConfigItem]):
"""批量更新配置"""
try:
updated_count = 0
errors = []
for item in configs:
try:
# 验证配置值
if item.type == 'number':
try:
float(item.value)
except (ValueError, TypeError):
errors.append(f"{item.key}: Invalid number value")
continue
# 特殊验证:百分比配置
if 'PERCENT' in item.key and item.type == 'number':
if not (0 <= float(item.value) <= 1):
errors.append(f"{item.key}: Must be between 0 and 1")
continue
TradingConfig.set(
item.key,
item.value,
item.type,
item.category,
item.description
)
updated_count += 1
except Exception as e:
errors.append(f"{item.key}: {str(e)}")
if errors:
return {
"message": f"部分配置更新成功: {updated_count}/{len(configs)}",
"updated": updated_count,
"errors": errors,
"note": "交易系统将在下次扫描时自动使用新配置"
}
return {
"message": f"成功更新 {updated_count} 个配置",
"updated": updated_count,
"note": "交易系统将在下次扫描时自动使用新配置"
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))