286 lines
11 KiB
Python
286 lines
11 KiB
Python
"""
|
||
配置管理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("/{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))
|
||
|
||
|
||
@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)
|
||
leverage = TradingConfig.get_value('LEVERAGE', 10)
|
||
|
||
# 计算最小保证金要求对应的最小仓位价值
|
||
required_position_value = min_margin_usdt * leverage
|
||
required_position_percent = required_position_value / available_balance if available_balance > 0 else 0
|
||
|
||
# 检查是否可行
|
||
is_feasible = required_position_percent <= max_position_percent
|
||
|
||
suggestions = []
|
||
|
||
if not is_feasible:
|
||
# 不可行,给出建议
|
||
# 方案1:降低最小保证金
|
||
suggested_min_margin = (available_balance * max_position_percent) / leverage
|
||
suggestions.append({
|
||
"type": "reduce_min_margin",
|
||
"title": "降低最小保证金",
|
||
"description": f"将 MIN_MARGIN_USDT 调整为 {suggested_min_margin:.2f} USDT(当前: {min_margin_usdt:.2f} USDT)",
|
||
"config_key": "MIN_MARGIN_USDT",
|
||
"suggested_value": round(suggested_min_margin, 2),
|
||
"reason": f"当前配置需要 {required_position_percent*100:.1f}% 的仓位价值,但最大允许 {max_position_percent*100:.1f}%"
|
||
})
|
||
|
||
# 方案2:增加账户余额
|
||
required_balance = required_position_value / max_position_percent
|
||
suggestions.append({
|
||
"type": "increase_balance",
|
||
"title": "增加账户余额",
|
||
"description": f"将账户余额增加到至少 {required_balance:.2f} USDT(当前: {available_balance:.2f} USDT)",
|
||
"config_key": None,
|
||
"suggested_value": round(required_balance, 2),
|
||
"reason": f"当前余额不足以满足最小保证金 {min_margin_usdt:.2f} USDT 的要求"
|
||
})
|
||
|
||
# 方案3:降低杠杆倍数
|
||
suggested_leverage = int((available_balance * max_position_percent) / min_margin_usdt)
|
||
if suggested_leverage >= 1:
|
||
suggestions.append({
|
||
"type": "reduce_leverage",
|
||
"title": "降低杠杆倍数",
|
||
"description": f"将 LEVERAGE 调整为 {suggested_leverage}x(当前: {leverage}x)",
|
||
"config_key": "LEVERAGE",
|
||
"suggested_value": suggested_leverage,
|
||
"reason": f"降低杠杆可以减少所需仓位价值"
|
||
})
|
||
else:
|
||
# 可行,显示当前配置信息
|
||
actual_min_position_value = available_balance * min_position_percent
|
||
actual_min_margin = actual_min_position_value / leverage if leverage > 0 else actual_min_position_value
|
||
|
||
suggestions.append({
|
||
"type": "info",
|
||
"title": "配置可行",
|
||
"description": f"当前配置可以正常下单。最小仓位价值: {actual_min_position_value:.2f} USDT,对应保证金: {actual_min_margin:.2f} USDT",
|
||
"config_key": None,
|
||
"suggested_value": None,
|
||
"reason": None
|
||
})
|
||
|
||
return {
|
||
"feasible": is_feasible,
|
||
"account_balance": available_balance,
|
||
"leverage": 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": required_position_value,
|
||
"required_position_percent": required_position_percent * 100,
|
||
"max_allowed_position_percent": max_position_percent * 100
|
||
},
|
||
"suggestions": suggestions
|
||
}
|
||
except Exception as e:
|
||
logger.error(f"检查配置可行性失败: {e}", exc_info=True)
|
||
raise HTTPException(status_code=500, detail=f"检查配置可行性失败: {str(e)}")
|