From 6c04202a552438afdaf5706ab895c9c6089aa6f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Tue, 13 Jan 2026 23:44:42 +0800 Subject: [PATCH] a --- frontend/src/components/ConfigPanel.css | 65 ++++++++++++- frontend/src/components/ConfigPanel.jsx | 58 +++++++++++- trading_system/binance_client.py | 117 +++++++++++++++++++++++- 3 files changed, 232 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/ConfigPanel.css b/frontend/src/components/ConfigPanel.css index 6f289d4..bb3715e 100644 --- a/frontend/src/components/ConfigPanel.css +++ b/frontend/src/components/ConfigPanel.css @@ -38,12 +38,53 @@ margin-top: 1.5rem; } +.preset-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + flex-wrap: wrap; + gap: 1rem; +} + .preset-section h3 { - margin: 0 0 1rem 0; + margin: 0; color: #34495e; font-size: 1rem; } +.current-preset-status { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.status-label { + font-size: 0.9rem; + color: #666; + font-weight: 500; +} + +.status-badge { + padding: 0.4rem 0.8rem; + border-radius: 20px; + font-size: 0.85rem; + font-weight: 600; + transition: all 0.3s; +} + +.status-badge.preset { + background: #4CAF50; + color: white; + box-shadow: 0 2px 4px rgba(76, 175, 80, 0.3); +} + +.status-badge.custom { + background: #FF9800; + color: white; + box-shadow: 0 2px 4px rgba(255, 152, 0, 0.3); +} + .preset-buttons { display: flex; gap: 1rem; @@ -69,6 +110,19 @@ box-shadow: 0 4px 8px rgba(0,0,0,0.1); } +.preset-btn.active { + border-color: #4CAF50; + background: #e8f5e9; + box-shadow: 0 2px 6px rgba(76, 175, 80, 0.2); +} + +.preset-btn.active:hover:not(:disabled) { + border-color: #4CAF50; + background: #c8e6c9; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(76, 175, 80, 0.3); +} + .preset-btn:disabled { opacity: 0.6; cursor: not-allowed; @@ -78,6 +132,15 @@ font-weight: bold; color: #2c3e50; margin-bottom: 0.25rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.active-indicator { + color: #4CAF50; + font-size: 1.2rem; + font-weight: bold; } .preset-desc { diff --git a/frontend/src/components/ConfigPanel.jsx b/frontend/src/components/ConfigPanel.jsx index 2d00b3c..6081e23 100644 --- a/frontend/src/components/ConfigPanel.jsx +++ b/frontend/src/components/ConfigPanel.jsx @@ -88,6 +88,45 @@ const ConfigPanel = () => { } } + // 检测当前配置匹配哪个预设方案 + const detectCurrentPreset = () => { + if (!configs || Object.keys(configs).length === 0) return null + + for (const [presetKey, preset] of Object.entries(presets)) { + let match = true + for (const [key, expectedValue] of Object.entries(preset.configs)) { + const currentConfig = configs[key] + if (!currentConfig) { + match = false + break + } + + // 获取当前值(处理百分比转换) + let currentValue = currentConfig.value + if (key.includes('PERCENT')) { + currentValue = currentValue * 100 + } + + // 比较值(允许小的浮点数误差) + if (typeof expectedValue === 'number' && typeof currentValue === 'number') { + if (Math.abs(currentValue - expectedValue) > 0.01) { + match = false + break + } + } else if (currentValue !== expectedValue) { + match = false + break + } + } + + if (match) { + return presetKey + } + } + + return null + } + const applyPreset = async (presetKey) => { const preset = presets[presetKey] if (!preset) return @@ -122,6 +161,8 @@ const ConfigPanel = () => { } } + const currentPreset = detectCurrentPreset() + if (loading) return
加载中...
const configCategories = { @@ -145,17 +186,28 @@ const ConfigPanel = () => { {/* 预设方案快速切换 */}
-

快速切换方案

+
+

快速切换方案

+
+ 当前方案: + + {currentPreset ? presets[currentPreset].name : '自定义'} + +
+
{Object.entries(presets).map(([key, preset]) => ( ))} diff --git a/trading_system/binance_client.py b/trading_system/binance_client.py index b7f513b..477ffa6 100644 --- a/trading_system/binance_client.py +++ b/trading_system/binance_client.py @@ -38,6 +38,7 @@ class BinanceClient: self.socket_manager: Optional[BinanceSocketManager] = None self.unicorn_manager: Optional[UnicornWebSocketManager] = None self.use_unicorn = config.TRADING_CONFIG.get('USE_UNICORN_WEBSOCKET', True) + self._symbol_info_cache: Dict[str, Dict] = {} # 缓存交易对信息 async def connect(self, timeout: int = None, retries: int = None): """ @@ -294,6 +295,97 @@ class BinanceClient: logger.error(f"获取持仓信息失败: {e}") return [] + async def get_symbol_info(self, symbol: str) -> Optional[Dict]: + """ + 获取交易对的精度和限制信息 + + Args: + symbol: 交易对 + + Returns: + 交易对信息字典,包含 quantityPrecision, minQty, stepSize 等 + """ + # 先检查缓存 + if symbol in self._symbol_info_cache: + return self._symbol_info_cache[symbol] + + try: + exchange_info = await self.client.futures_exchange_info() + for s in exchange_info['symbols']: + if s['symbol'] == symbol: + # 提取数量精度信息 + quantity_precision = s.get('quantityPrecision', 8) + + # 从filters中提取minQty和stepSize + min_qty = None + step_size = None + for f in s.get('filters', []): + if f['filterType'] == 'LOT_SIZE': + min_qty = float(f.get('minQty', 0)) + step_size = float(f.get('stepSize', 0)) + break + + info = { + 'quantityPrecision': quantity_precision, + 'minQty': min_qty or 0, + 'stepSize': step_size or 0 + } + + # 缓存信息 + self._symbol_info_cache[symbol] = info + logger.debug(f"获取 {symbol} 精度信息: {info}") + return info + + logger.warning(f"未找到交易对 {symbol} 的信息") + return None + except Exception as e: + logger.error(f"获取 {symbol} 交易对信息失败: {e}") + return None + + def _adjust_quantity_precision(self, quantity: float, symbol_info: Dict) -> float: + """ + 调整数量精度,使其符合币安要求 + + Args: + quantity: 原始数量 + symbol_info: 交易对信息 + + Returns: + 调整后的数量 + """ + if not symbol_info: + # 如果没有交易对信息,使用默认精度(3位小数) + return round(quantity, 3) + + quantity_precision = symbol_info.get('quantityPrecision', 8) + step_size = symbol_info.get('stepSize', 0) + min_qty = symbol_info.get('minQty', 0) + + # 如果有stepSize,按照stepSize调整 + if step_size > 0: + # 向下取整到stepSize的倍数(使用浮点数除法) + adjusted = float(int(quantity / step_size)) * step_size + else: + # 否则按照精度调整 + adjusted = round(quantity, quantity_precision) + + # 确保不小于最小数量 + if min_qty > 0 and adjusted < min_qty: + # 如果小于最小数量,尝试向上取整到最小数量 + if step_size > 0: + adjusted = min_qty + else: + adjusted = round(min_qty, quantity_precision) + logger.warning(f"数量 {quantity} 小于最小数量 {min_qty},调整为 {adjusted}") + + # 最终精度调整 + adjusted = round(adjusted, quantity_precision) + + if adjusted != quantity: + logger.info(f"数量精度调整: {quantity} -> {adjusted} (精度: {quantity_precision}, stepSize: {step_size}, minQty: {min_qty})") + + return adjusted + async def place_order( self, symbol: str, @@ -316,12 +408,22 @@ class BinanceClient: 订单信息 """ try: + # 获取交易对精度信息并调整数量 + symbol_info = await self.get_symbol_info(symbol) + adjusted_quantity = self._adjust_quantity_precision(quantity, symbol_info) + + if adjusted_quantity <= 0: + logger.error(f"调整后的数量无效: {adjusted_quantity} (原始: {quantity})") + return None + + logger.info(f"下单: {symbol} {side} {adjusted_quantity} (原始: {quantity}) @ {order_type}") + if order_type == 'MARKET': order = await self.client.futures_create_order( symbol=symbol, side=side, type='MARKET', - quantity=quantity + quantity=adjusted_quantity ) else: if price is None: @@ -331,14 +433,21 @@ class BinanceClient: side=side, type='LIMIT', timeInForce='GTC', - quantity=quantity, + quantity=adjusted_quantity, price=price ) - logger.info(f"下单成功: {symbol} {side} {quantity} @ {order_type}") + logger.info(f"下单成功: {symbol} {side} {adjusted_quantity} @ {order_type}") return order except BinanceAPIException as e: - logger.error(f"下单失败 {symbol} {side}: {e}") + error_code = e.code if hasattr(e, 'code') else None + if error_code == -1111: + logger.error(f"下单失败 {symbol} {side}: 精度错误 - {e}") + logger.error(f" 原始数量: {quantity}") + if symbol_info: + logger.error(f" 交易对精度: {symbol_info}") + else: + logger.error(f"下单失败 {symbol} {side}: {e}") return None async def cancel_order(self, symbol: str, order_id: int) -> bool: