From 5c841621f7ca5e6f19a2a399e620f26a6f152391 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 14:30:57 +0800 Subject: [PATCH] a --- README.md | 173 +++++++++++- binance_client.py | 302 +++++++++++++++++++++ config.example.py | 45 +++ config.py | 44 +++ cursor_binance_automated_trading_system.md | 216 +++++++++++++++ main.py | 105 +++++++ market_scanner.py | 152 +++++++++++ position_manager.py | 259 ++++++++++++++++++ requirements.txt | 3 + risk_manager.py | 255 +++++++++++++++++ strategy.py | 193 +++++++++++++ 11 files changed, 1746 insertions(+), 1 deletion(-) create mode 100644 binance_client.py create mode 100644 config.example.py create mode 100644 config.py create mode 100644 cursor_binance_automated_trading_system.md create mode 100644 main.py create mode 100644 market_scanner.py create mode 100644 position_manager.py create mode 100644 requirements.txt create mode 100644 risk_manager.py create mode 100644 strategy.py diff --git a/README.md b/README.md index b203e21..6b713d5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,173 @@ -# auto_trade_sys +# 币安自动交易系统 (Binance Automated Trading System) + +基于币安API的Python自动交易系统,实现自动发现涨跌幅最大的货币对并执行交易策略。 + +## 功能特性 + +1. **自动市场扫描**:每5分钟扫描所有USDT永续合约,发现涨跌幅最大的前10个货币对 +2. **智能交易**:顺着波动方向自动下单(涨幅做多,跌幅做空) +3. **严格仓位控制**: + - 单笔最大仓位:账户余额的5% + - 总仓位上限:账户余额的30% + - 单笔最小仓位:账户余额的1% +4. **风险控制**: + - 自动止损:3% + - 自动止盈:5% + - 成交量过滤:只交易成交量大的币对 + - 趋势确认:结合多时间周期确认趋势 +5. **实时监控**:持续监控持仓,自动执行止损止盈 + +## 项目结构 + +``` +auto_trade_sys/ +├── config.py # 配置文件(API密钥、交易参数) +├── binance_client.py # 币安客户端封装 +├── market_scanner.py # 市场扫描器 +├── risk_manager.py # 风险管理模块 +├── position_manager.py # 仓位管理模块 +├── strategy.py # 交易策略 +├── main.py # 主程序入口 +├── requirements.txt # Python依赖 +└── README.md # 项目说明 +``` + +## 安装步骤 + +### 1. 安装Python依赖 + +```bash +pip install -r requirements.txt +``` + +### 2. 配置API密钥 + +有两种方式配置API密钥: + +**方式1:环境变量(推荐)** + +```bash +export BINANCE_API_KEY="your_api_key" +export BINANCE_API_SECRET="your_api_secret" +export USE_TESTNET="True" # 测试网模式,生产环境设为False +``` + +**方式2:直接修改 config.py** + +编辑 `config.py` 文件,填入你的API密钥: + +```python +BINANCE_API_KEY = 'your_api_key' +BINANCE_API_SECRET = 'your_api_secret' +USE_TESTNET = True # 测试网模式 +``` + +### 3. 调整交易参数(可选) + +编辑 `config.py` 中的 `TRADING_CONFIG` 字典,根据你的需求调整参数: + +```python +TRADING_CONFIG = { + 'MAX_POSITION_PERCENT': 0.05, # 单笔最大仓位:5% + 'MAX_TOTAL_POSITION_PERCENT': 0.30, # 总仓位上限:30% + 'MIN_CHANGE_PERCENT': 2.0, # 最小涨跌幅阈值:2% + 'STOP_LOSS_PERCENT': 0.03, # 止损:3% + 'TAKE_PROFIT_PERCENT': 0.05, # 止盈:5% + # ... 更多参数 +} +``` + +## 使用方法 + +### 启动交易系统 + +```bash +python main.py +``` + +### 停止交易系统 + +按 `Ctrl+C` 停止程序。 + +## 交易策略说明 + +### 1. 市场扫描 + +- 每5分钟扫描一次市场 +- 获取所有USDT永续合约交易对 +- 计算5分钟涨跌幅 +- 过滤条件: + - 涨跌幅 >= 2% + - 24小时成交量 >= 1000万USDT +- 选择涨跌幅绝对值最大的前10个货币对 + +### 2. 交易执行 + +- **做多**:涨幅 > 阈值时买入 +- **做空**:跌幅 > 阈值时卖出 +- 默认杠杆:10倍 +- 使用市价单快速成交 + +### 3. 风险控制 + +- **仓位控制**:严格限制单笔和总仓位 +- **止损止盈**:自动设置止损和止盈价格 +- **成交量确认**:只交易成交量大的币对 +- **趋势确认**:结合15分钟K线确认趋势 + +### 4. 持仓管理 + +- 实时监控持仓价格 +- 自动触发止损止盈 +- 定期打印持仓摘要 + +## 注意事项 + +⚠️ **重要提示**: + +1. **测试网模式**:首次使用建议开启测试网模式(`USE_TESTNET=True`)进行测试 +2. **API权限**:确保API密钥具有合约交易权限,但**不要**开启提币权限 +3. **资金安全**:建议先用小额资金测试,确认策略符合预期后再增加资金 +4. **风险提示**:加密货币交易存在高风险,可能导致资金损失,请谨慎使用 +5. **网络稳定**:确保网络连接稳定,避免因网络问题导致订单异常 + +## 日志 + +程序运行日志会同时输出到: +- 控制台(标准输出) +- 日志文件:`trading_bot.log` + +日志级别可通过环境变量 `LOG_LEVEL` 设置(默认:INFO) + +## 依赖库 + +- `python-binance==1.0.19`:币安API客户端 +- `websocket-client==1.6.1`:WebSocket支持 +- `aiohttp==3.9.1`:异步HTTP客户端 + +## 常见问题 + +### Q: 如何查看当前持仓? + +A: 程序运行时会定期打印持仓摘要,包含持仓数量、盈亏等信息。 + +### Q: 如何修改扫描间隔? + +A: 修改 `config.py` 中的 `SCAN_INTERVAL` 参数(单位:秒)。 + +### Q: 支持其他交易所吗? + +A: 当前版本仅支持币安,如需支持其他交易所,可以修改 `binance_client.py` 或使用 `ccxt` 库。 + +### Q: 如何回测策略? + +A: 当前版本不支持回测,建议使用币安测试网进行实盘测试。 + +## 许可证 + +本项目仅供学习和研究使用,使用者需自行承担交易风险。 + +## 更新日志 + +- 2026-01-13:初始版本,实现基础自动交易功能 diff --git a/binance_client.py b/binance_client.py new file mode 100644 index 0000000..16ebf4e --- /dev/null +++ b/binance_client.py @@ -0,0 +1,302 @@ +""" +币安客户端封装 - 提供异步交易接口 +""" +import asyncio +import logging +from typing import Dict, List, Optional, Any +from binance import AsyncClient, BinanceSocketManager +from binance.exceptions import BinanceAPIException +import config + +logger = logging.getLogger(__name__) + + +class BinanceClient: + """币安客户端封装类""" + + def __init__(self, api_key: str = None, api_secret: str = None, testnet: bool = False): + """ + 初始化币安客户端 + + Args: + api_key: API密钥 + api_secret: API密钥 + testnet: 是否使用测试网 + """ + self.api_key = api_key or config.BINANCE_API_KEY + self.api_secret = api_secret or config.BINANCE_API_SECRET + self.testnet = testnet or config.USE_TESTNET + self.client: Optional[AsyncClient] = None + self.socket_manager: Optional[BinanceSocketManager] = None + + async def connect(self, timeout: int = None, retries: int = None): + """ + 连接币安API + + Args: + timeout: 连接超时时间(秒),默认从config读取 + retries: 重试次数,默认从config读取 + """ + timeout = timeout or config.CONNECTION_TIMEOUT + retries = retries or config.CONNECTION_RETRIES + + last_error = None + for attempt in range(retries): + try: + logger.info( + f"尝试连接币安API (第 {attempt + 1}/{retries} 次, " + f"测试网: {self.testnet}, 超时: {timeout}秒)..." + ) + + # 创建客户端 + self.client = await AsyncClient.create( + api_key=self.api_key, + api_secret=self.api_secret, + testnet=self.testnet + ) + + # 测试连接(带超时) + try: + await asyncio.wait_for(self.client.ping(), timeout=timeout) + except asyncio.TimeoutError: + await self.client.close_connection() + raise asyncio.TimeoutError(f"ping超时 ({timeout}秒)") + + self.socket_manager = BinanceSocketManager(self.client) + logger.info(f"✓ 币安客户端连接成功 (测试网: {self.testnet})") + return + + except asyncio.TimeoutError as e: + last_error = f"连接超时: {e}" + logger.warning(f"连接超时,剩余 {retries - attempt - 1} 次重试机会") + if attempt < retries - 1: + await asyncio.sleep(2) # 等待2秒后重试 + except Exception as e: + last_error = str(e) + logger.warning(f"连接失败: {e},剩余 {retries - attempt - 1} 次重试机会") + if self.client: + try: + await self.client.close_connection() + except: + pass + if attempt < retries - 1: + await asyncio.sleep(2) + + error_msg = f"连接币安API失败 (已重试 {retries} 次): {last_error}" + logger.error("=" * 60) + logger.error(error_msg) + logger.error("=" * 60) + logger.error("故障排查建议:") + logger.error("1. 检查网络连接是否正常") + logger.error("2. 检查API密钥是否正确") + logger.error("3. 如果在中国大陆,可能需要使用代理或VPN") + if self.testnet: + logger.error("4. 测试网地址可能无法访问,尝试设置 USE_TESTNET=False") + logger.error("5. 检查防火墙设置") + logger.error("=" * 60) + raise ConnectionError(error_msg) + + async def disconnect(self): + """断开连接""" + if self.client: + await self.client.close_connection() + logger.info("币安客户端已断开连接") + + async def get_all_usdt_pairs(self) -> List[str]: + """ + 获取所有USDT交易对 + + Returns: + USDT交易对列表 + """ + try: + # 获取合约市场信息 + exchange_info = await self.client.futures_exchange_info() + usdt_pairs = [ + symbol['symbol'] + for symbol in exchange_info['symbols'] + if symbol['symbol'].endswith('USDT') + and symbol['status'] == 'TRADING' + and symbol.get('contractType') == 'PERPETUAL' # U本位永续合约 + ] + logger.info(f"获取到 {len(usdt_pairs)} 个USDT永续合约交易对") + return usdt_pairs + except BinanceAPIException as e: + logger.error(f"获取交易对失败: {e}") + return [] + + async def get_klines(self, symbol: str, interval: str = '5m', limit: int = 2) -> List[List]: + """ + 获取K线数据(合约市场) + + Args: + symbol: 交易对 + interval: K线周期 + limit: 获取数量 + + Returns: + K线数据列表 + """ + try: + klines = await self.client.futures_klines(symbol=symbol, interval=interval, limit=limit) + return klines + except BinanceAPIException as e: + logger.error(f"获取 {symbol} K线数据失败: {e}") + return [] + + async def get_ticker_24h(self, symbol: str) -> Optional[Dict]: + """ + 获取24小时行情数据(合约市场) + + Args: + symbol: 交易对 + + Returns: + 24小时行情数据 + """ + try: + ticker = await self.client.futures_symbol_ticker(symbol=symbol) + stats = await self.client.futures_ticker(symbol=symbol) + return { + 'symbol': symbol, + 'price': float(ticker['price']), + 'volume': float(stats.get('quoteVolume', 0)), + 'changePercent': float(stats.get('priceChangePercent', 0)) + } + except BinanceAPIException as e: + logger.error(f"获取 {symbol} 24小时行情失败: {e}") + return None + + async def get_account_balance(self) -> Dict[str, float]: + """ + 获取U本位合约账户余额 + + Returns: + 账户余额字典 {'available': 可用余额, 'total': 总余额} + """ + try: + account = await self.client.futures_account() + assets = account.get('assets', []) + usdt_asset = next((a for a in assets if a['asset'] == 'USDT'), None) + + if usdt_asset: + return { + 'available': float(usdt_asset['availableBalance']), + 'total': float(usdt_asset['walletBalance']), + 'margin': float(usdt_asset['marginBalance']) + } + return {'available': 0.0, 'total': 0.0, 'margin': 0.0} + except BinanceAPIException as e: + logger.error(f"获取账户余额失败: {e}") + return {'available': 0.0, 'total': 0.0, 'margin': 0.0} + + async def get_open_positions(self) -> List[Dict]: + """ + 获取当前持仓 + + Returns: + 持仓列表 + """ + try: + positions = await self.client.futures_position_information() + open_positions = [ + { + 'symbol': pos['symbol'], + 'positionAmt': float(pos['positionAmt']), + 'entryPrice': float(pos['entryPrice']), + 'markPrice': float(pos.get('markPrice', 0)), + 'unRealizedProfit': float(pos['unRealizedProfit']), + 'leverage': int(pos['leverage']) + } + for pos in positions + if float(pos['positionAmt']) != 0 + ] + return open_positions + except BinanceAPIException as e: + logger.error(f"获取持仓信息失败: {e}") + return [] + + async def place_order( + self, + symbol: str, + side: str, + quantity: float, + order_type: str = 'MARKET', + price: Optional[float] = None + ) -> Optional[Dict]: + """ + 下单 + + Args: + symbol: 交易对 + side: 方向 'BUY' 或 'SELL' + quantity: 数量 + order_type: 订单类型 'MARKET' 或 'LIMIT' + price: 限价单价格 + + Returns: + 订单信息 + """ + try: + if order_type == 'MARKET': + order = await self.client.futures_create_order( + symbol=symbol, + side=side, + type='MARKET', + quantity=quantity + ) + else: + if price is None: + raise ValueError("限价单必须指定价格") + order = await self.client.futures_create_order( + symbol=symbol, + side=side, + type='LIMIT', + timeInForce='GTC', + quantity=quantity, + price=price + ) + + logger.info(f"下单成功: {symbol} {side} {quantity} @ {order_type}") + return order + except BinanceAPIException as e: + logger.error(f"下单失败 {symbol} {side}: {e}") + return None + + async def cancel_order(self, symbol: str, order_id: int) -> bool: + """ + 取消订单 + + Args: + symbol: 交易对 + order_id: 订单ID + + Returns: + 是否成功 + """ + try: + await self.client.futures_cancel_order(symbol=symbol, orderId=order_id) + logger.info(f"取消订单成功: {symbol} {order_id}") + return True + except BinanceAPIException as e: + logger.error(f"取消订单失败: {e}") + return False + + async def set_leverage(self, symbol: str, leverage: int = 10) -> bool: + """ + 设置杠杆倍数 + + Args: + symbol: 交易对 + leverage: 杠杆倍数 + + Returns: + 是否成功 + """ + try: + await self.client.futures_change_leverage(symbol=symbol, leverage=leverage) + logger.info(f"设置杠杆成功: {symbol} {leverage}x") + return True + except BinanceAPIException as e: + logger.error(f"设置杠杆失败: {e}") + return False diff --git a/config.example.py b/config.example.py new file mode 100644 index 0000000..0ed7237 --- /dev/null +++ b/config.example.py @@ -0,0 +1,45 @@ +""" +配置文件示例 - 复制此文件为 config.py 并填入你的API密钥 +""" +import os +from typing import Optional + +# 币安API配置 +# 方式1:从环境变量读取(推荐) +BINANCE_API_KEY: Optional[str] = os.getenv('BINANCE_API_KEY', '') +BINANCE_API_SECRET: Optional[str] = os.getenv('BINANCE_API_SECRET', '') + +# 方式2:直接填写(不推荐,容易泄露) +# BINANCE_API_KEY = 'your_api_key_here' +# BINANCE_API_SECRET = 'your_api_secret_here' + +# 测试网配置(开发时使用) +USE_TESTNET: bool = os.getenv('USE_TESTNET', 'True').lower() == 'true' + +# 交易参数配置 +TRADING_CONFIG = { + # 仓位控制 + 'MAX_POSITION_PERCENT': 0.05, # 单笔最大仓位:账户余额的5% + 'MAX_TOTAL_POSITION_PERCENT': 0.30, # 总仓位上限:账户余额的30% + 'MIN_POSITION_PERCENT': 0.01, # 单笔最小仓位:账户余额的1% + + # 涨跌幅阈值 + 'MIN_CHANGE_PERCENT': 2.0, # 最小涨跌幅阈值:2% + 'TOP_N_SYMBOLS': 10, # 选择前N个货币对 + + # 风险控制 + 'STOP_LOSS_PERCENT': 0.03, # 止损:3% + 'TAKE_PROFIT_PERCENT': 0.05, # 止盈:5% + + # 市场扫描 + 'SCAN_INTERVAL': 300, # 扫描间隔:5分钟(秒) + 'KLINE_INTERVAL': '5m', # K线周期:5分钟 + + # 过滤条件 + 'MIN_VOLUME_24H': 10000000, # 最小24小时成交量:1000万USDT + 'MIN_VOLATILITY': 0.02, # 最小波动率:2% +} + +# 日志配置 +LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO') +LOG_FILE = 'trading_bot.log' diff --git a/config.py b/config.py new file mode 100644 index 0000000..e364051 --- /dev/null +++ b/config.py @@ -0,0 +1,44 @@ +""" +配置文件 - API密钥和交易参数配置 +""" +import os +from typing import Optional + +# 币安API配置 +BINANCE_API_KEY: Optional[str] = os.getenv('BINANCE_API_KEY', 'pMEXSgISMgpUIpGjyhikMXWQ7K7cCs1FFATyIvNIwWrUIQegoipVBskPUoUuvaVN') +BINANCE_API_SECRET: Optional[str] = os.getenv('BINANCE_API_SECRET', 'RklItVtBCjGV40mIquoSj78xlTGkdUxz0AFyTnsnuzSBfx776VG0S2Vw5BRLRRg2') + +# 测试网配置(开发时使用) +USE_TESTNET: bool = os.getenv('USE_TESTNET', 'True').lower() == 'true' + +# 交易参数配置 +TRADING_CONFIG = { + # 仓位控制 + 'MAX_POSITION_PERCENT': 0.05, # 单笔最大仓位:账户余额的5% + 'MAX_TOTAL_POSITION_PERCENT': 0.30, # 总仓位上限:账户余额的30% + 'MIN_POSITION_PERCENT': 0.01, # 单笔最小仓位:账户余额的1% + + # 涨跌幅阈值 + 'MIN_CHANGE_PERCENT': 2.0, # 最小涨跌幅阈值:2% + 'TOP_N_SYMBOLS': 10, # 选择前N个货币对 + + # 风险控制 + 'STOP_LOSS_PERCENT': 0.03, # 止损:3% + 'TAKE_PROFIT_PERCENT': 0.05, # 止盈:5% + + # 市场扫描 + 'SCAN_INTERVAL': 300, # 扫描间隔:5分钟(秒) + 'KLINE_INTERVAL': '5m', # K线周期:5分钟 + + # 过滤条件 + 'MIN_VOLUME_24H': 10000000, # 最小24小时成交量:1000万USDT + 'MIN_VOLATILITY': 0.02, # 最小波动率:2% +} + +# 连接配置 +CONNECTION_TIMEOUT = int(os.getenv('CONNECTION_TIMEOUT', '30')) # 连接超时时间(秒) +CONNECTION_RETRIES = int(os.getenv('CONNECTION_RETRIES', '3')) # 连接重试次数 + +# 日志配置 +LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO') +LOG_FILE = 'trading_bot.log' diff --git a/cursor_binance_automated_trading_system.md b/cursor_binance_automated_trading_system.md new file mode 100644 index 0000000..6ab7b32 --- /dev/null +++ b/cursor_binance_automated_trading_system.md @@ -0,0 +1,216 @@ +# Binance automated trading system language choice +_Exported on 2026/1/13 at GMT+8 14:11:19 from Cursor (2.3.34)_ + +--- + +**User** + +我想创建一个基于币安API的自动交易系统,使用java或是nodejs或是python编写,帮我看看用哪种语言哪种框架更好的支持 +主要的需求: +1、自动发现所有货币中涨幅或是跌幅在5分钟内容最大的前10个货币对 +2、然后顺着波动的方向去下单,基于U本位合约账户的余额 +3、严格控制下单的仓位 +4、可能的一些优化,提高胜率 + +--- + +**Cursor** + +分析三种语言和框架的适用性。先查看项目结构,了解现有技术栈。 + + +基于需求,分析三种语言和框架的适用性: + + +基于你的需求,三种语言的对比与建议如下: + +## 语言和框架推荐分析 + +### **推荐:Python(首选)** + +**优势:** +1. 生态完善:`python-binance`、`ccxt` 等库成熟,文档齐全 +2. 开发效率高:语法简洁,适合快速迭代策略 +3. 数据分析:`pandas`、`numpy` 便于计算涨跌幅和排序 +4. 实时性:WebSocket 支持良好,适合实时监控价格 +5. 社区资源多:策略示例和教程丰富 + +**推荐框架/库:** +- `python-binance`:官方推荐,功能完整 +- `ccxt`:统一接口,支持多交易所 +- `asyncio` + `websockets`:异步处理,性能好 +- `pandas`:数据处理和计算 + +**示例架构:** +```python +# 主要组件 +- binance.client.AsyncClient # 异步客户端 +- websocket 监听价格变化 +- pandas 计算5分钟涨跌幅 +- 仓位管理模块 +- 风险控制模块 +``` + +--- + +### **次选:Node.js** + +**优势:** +1. 异步性能好:事件驱动,适合高频请求 +2. WebSocket 原生支持:实时数据流处理 +3. 部署简单:轻量级,易于部署和维护 + +**劣势:** +1. 数据处理不如 Python 方便 +2. 数值计算精度需注意(BigNumber) +3. 生态相对 Python 略少 + +**推荐库:** +- `binance-api-node` 或 `node-binance-api` +- `ws`:WebSocket 客户端 +- `decimal.js`:精确数值计算 + +--- + +### **不推荐:Java** + +**劣势:** +1. 开发效率低:代码量大,迭代慢 +2. 实时性:WebSocket 支持不如前两者直观 +3. 生态:币安相关库较少且更新慢 +4. 资源占用:内存和 CPU 开销较大 + +**适用场景:** 仅在企业级、需要与现有 Java 系统集成时考虑 + +--- + +## 针对你的需求的具体建议 + +### 1. **自动发现涨跌幅最大的前10个货币对** + +**Python 实现思路:** +```python +# 使用 pandas 计算5分钟涨跌幅 +import pandas as pd +from binance.client import Client + +# 获取所有USDT交易对 +symbols = client.get_all_tickers() +usdt_pairs = [s for s in symbols if s['symbol'].endswith('USDT')] + +# 计算5分钟K线数据 +klines = client.get_klines(symbol=symbol, interval='5m', limit=2) +current_price = float(klines[-1][4]) # 最新收盘价 +prev_price = float(klines[-2][4]) # 5分钟前价格 +change_pct = (current_price - prev_price) / prev_price * 100 + +# 排序取前10 +top_10 = sorted(pairs, key=lambda x: abs(x['change']), reverse=True)[:10] +``` + +### 2. **顺着波动方向下单** + +**关键点:** +- 涨幅 > 阈值 → 做多(买入) +- 跌幅 > 阈值 → 做空(卖出) +- 使用 `python-binance` 的合约交易接口 + +### 3. **严格控制仓位** + +**建议:** +- 单笔不超过账户余额的 2-5% +- 总仓位不超过 20-30% +- 使用 `position_amt` 和 `available_balance` 精确控制 + +### 4. **优化提高胜率** + +**策略建议:** +- 成交量过滤:只交易成交量大的币对 +- 趋势确认:结合多时间周期(5m + 15m) +- 止损止盈:设置自动止损止盈 +- 避免震荡:过滤波动率过小的币对 +- 滑点控制:使用限价单而非市价单 + +--- + +## 最终推荐方案 + +**技术栈:Python + python-binance + asyncio** + +**项目结构建议:** +``` +trading_bot/ +├── config.py # API密钥配置 +├── binance_client.py # 币安客户端封装 +├── market_scanner.py # 市场扫描器(发现涨跌幅) +├── position_manager.py # 仓位管理器 +├── risk_manager.py # 风险管理 +├── strategy.py # 交易策略 +├── main.py # 主程序 +└── requirements.txt # 依赖 +``` + +**核心依赖:** +``` +python-binance==1.0.19 +pandas==2.0.3 +websocket-client==1.6.1 +asyncio +``` + +**为什么选 Python:** +1. 开发速度快,便于快速验证策略 +2. 数据处理方便,计算涨跌幅和排序简单 +3. 实时监控:WebSocket 集成容易 +4. 社区支持好,遇到问题容易解决 +5. 便于后续扩展:机器学习、回测等 + +需要我提供具体的代码实现框架吗?我可以给出完整的项目结构和核心代码示例。 + +```python +# 主要组件 +- binance.client.AsyncClient # 异步客户端 +- websocket 监听价格变化 +- pandas 计算5分钟涨跌幅 +- 仓位管理模块 +- 风险控制模块 +``` + +```python +# 使用 pandas 计算5分钟涨跌幅 +import pandas as pd +from binance.client import Client + +# 获取所有USDT交易对 +symbols = client.get_all_tickers() +usdt_pairs = [s for s in symbols if s['symbol'].endswith('USDT')] + +# 计算5分钟K线数据 +klines = client.get_klines(symbol=symbol, interval='5m', limit=2) +current_price = float(klines[-1][4]) # 最新收盘价 +prev_price = float(klines[-2][4]) # 5分钟前价格 +change_pct = (current_price - prev_price) / prev_price * 100 + +# 排序取前10 +top_10 = sorted(pairs, key=lambda x: abs(x['change']), reverse=True)[:10] +``` + +```plaintext +trading_bot/ +├── config.py # API密钥配置 +├── binance_client.py # 币安客户端封装 +├── market_scanner.py # 市场扫描器(发现涨跌幅) +├── position_manager.py # 仓位管理器 +├── risk_manager.py # 风险管理 +├── strategy.py # 交易策略 +├── main.py # 主程序 +└── requirements.txt # 依赖 +``` + +```plaintext +python-binance==1.0.19 +pandas==2.0.3 +websocket-client==1.6.1 +asyncio +``` + diff --git a/main.py b/main.py new file mode 100644 index 0000000..ffc4690 --- /dev/null +++ b/main.py @@ -0,0 +1,105 @@ +""" +主程序 - 币安自动交易系统入口 +""" +import asyncio +import logging +import sys +from binance_client import BinanceClient +from market_scanner import MarketScanner +from risk_manager import RiskManager +from position_manager import PositionManager +from strategy import TradingStrategy +import config + +# 配置日志 +logging.basicConfig( + level=getattr(logging, config.LOG_LEVEL), + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler(config.LOG_FILE, encoding='utf-8'), + logging.StreamHandler(sys.stdout) + ] +) + +logger = logging.getLogger(__name__) + + +async def main(): + """主函数""" + logger.info("=" * 60) + logger.info("币安自动交易系统启动") + logger.info("=" * 60) + + # 检查API密钥 + if not config.BINANCE_API_KEY or not config.BINANCE_API_SECRET: + logger.error("请设置 BINANCE_API_KEY 和 BINANCE_API_SECRET 环境变量") + logger.error("或在 config.py 中直接配置") + return + + # 初始化组件 + client = None + try: + # 1. 初始化币安客户端 + logger.info("初始化币安客户端...") + logger.info(f"测试网模式: {config.USE_TESTNET}") + logger.info(f"连接超时: {config.CONNECTION_TIMEOUT}秒") + logger.info(f"重试次数: {config.CONNECTION_RETRIES}次") + + client = BinanceClient( + api_key=config.BINANCE_API_KEY, + api_secret=config.BINANCE_API_SECRET, + testnet=config.USE_TESTNET + ) + await client.connect() + + # 2. 检查账户余额 + balance = await client.get_account_balance() + logger.info( + f"账户余额: 总余额 {balance['total']:.2f} USDT, " + f"可用余额 {balance['available']:.2f} USDT" + ) + + if balance['available'] <= 0: + logger.error("账户可用余额不足,无法交易") + return + + # 3. 初始化各个模块 + logger.info("初始化交易模块...") + scanner = MarketScanner(client) + risk_manager = RiskManager(client) + position_manager = PositionManager(client, risk_manager) + strategy = TradingStrategy(client, scanner, risk_manager, position_manager) + + # 4. 打印配置信息 + logger.info("交易配置:") + logger.info(f" 单笔最大仓位: {config.TRADING_CONFIG['MAX_POSITION_PERCENT']*100:.1f}%") + logger.info(f" 总仓位上限: {config.TRADING_CONFIG['MAX_TOTAL_POSITION_PERCENT']*100:.1f}%") + logger.info(f" 最小涨跌幅阈值: {config.TRADING_CONFIG['MIN_CHANGE_PERCENT']:.1f}%") + logger.info(f" 扫描间隔: {config.TRADING_CONFIG['SCAN_INTERVAL']} 秒") + logger.info(f" 止损: {config.TRADING_CONFIG['STOP_LOSS_PERCENT']*100:.1f}%") + logger.info(f" 止盈: {config.TRADING_CONFIG['TAKE_PROFIT_PERCENT']*100:.1f}%") + logger.info(f" 测试网模式: {config.USE_TESTNET}") + logger.info("=" * 60) + + # 5. 启动交易策略 + logger.info("启动交易策略...") + await strategy.execute_strategy() + + except KeyboardInterrupt: + logger.info("收到停止信号,正在关闭...") + except Exception as e: + logger.error(f"程序运行出错: {e}", exc_info=True) + finally: + # 清理资源 + if client: + await client.disconnect() + logger.info("程序已退出") + + +if __name__ == '__main__': + try: + asyncio.run(main()) + except KeyboardInterrupt: + logger.info("程序被用户中断") + except Exception as e: + logger.error(f"程序异常退出: {e}", exc_info=True) diff --git a/market_scanner.py b/market_scanner.py new file mode 100644 index 0000000..f758344 --- /dev/null +++ b/market_scanner.py @@ -0,0 +1,152 @@ +""" +市场扫描器 - 发现涨跌幅最大的前N个货币对 +""" +import asyncio +import logging +from typing import List, Dict, Optional +from binance_client import BinanceClient +import config + +logger = logging.getLogger(__name__) + + +class MarketScanner: + """市场扫描器类""" + + def __init__(self, client: BinanceClient): + """ + 初始化市场扫描器 + + Args: + client: 币安客户端 + """ + self.client = client + self.top_symbols: List[Dict] = [] + + async def scan_market(self) -> List[Dict]: + """ + 扫描市场,找出涨跌幅最大的前N个货币对 + + Returns: + 前N个货币对列表,包含涨跌幅信息 + """ + logger.info("开始扫描市场...") + + # 获取所有USDT交易对 + symbols = await self.client.get_all_usdt_pairs() + if not symbols: + logger.warning("未获取到交易对") + return [] + + # 并发获取所有交易对的涨跌幅数据 + tasks = [self._get_symbol_change(symbol) for symbol in symbols] + results = await asyncio.gather(*tasks, return_exceptions=True) + + # 过滤有效结果 + valid_results = [ + r for r in results + if isinstance(r, dict) and r.get('changePercent') is not None + ] + + # 过滤最小涨跌幅和成交量 + filtered_results = [ + r for r in valid_results + if abs(r['changePercent']) >= config.TRADING_CONFIG['MIN_CHANGE_PERCENT'] + and r.get('volume24h', 0) >= config.TRADING_CONFIG['MIN_VOLUME_24H'] + ] + + # 按涨跌幅绝对值排序,取前N个 + sorted_results = sorted( + filtered_results, + key=lambda x: abs(x['changePercent']), + reverse=True + ) + + top_n = sorted_results[:config.TRADING_CONFIG['TOP_N_SYMBOLS']] + + self.top_symbols = top_n + logger.info(f"扫描完成,找到 {len(top_n)} 个符合条件的交易对") + + # 打印结果 + for i, symbol_info in enumerate(top_n, 1): + logger.info( + f"{i}. {symbol_info['symbol']}: " + f"{symbol_info['changePercent']:.2f}% " + f"(价格: {symbol_info['price']:.4f}, " + f"成交量: {symbol_info.get('volume24h', 0):.0f})" + ) + + return top_n + + async def _get_symbol_change(self, symbol: str) -> Optional[Dict]: + """ + 获取单个交易对的涨跌幅 + + Args: + symbol: 交易对 + + Returns: + 包含涨跌幅信息的字典 + """ + try: + # 获取24小时行情数据 + ticker = await self.client.get_ticker_24h(symbol) + if not ticker: + return None + + # 获取5分钟K线数据计算精确涨跌幅 + klines = await self.client.get_klines( + symbol=symbol, + interval=config.TRADING_CONFIG['KLINE_INTERVAL'], + limit=2 + ) + + if len(klines) < 2: + return None + + # 计算5分钟涨跌幅 + current_price = float(klines[-1][4]) # 最新收盘价 + prev_price = float(klines[-2][4]) # 5分钟前收盘价 + + if prev_price == 0: + return None + + change_percent = ((current_price - prev_price) / prev_price) * 100 + + return { + 'symbol': symbol, + 'price': current_price, + 'prevPrice': prev_price, + 'changePercent': change_percent, + 'volume24h': ticker.get('volume', 0), + 'direction': 'UP' if change_percent > 0 else 'DOWN' + } + except Exception as e: + logger.debug(f"获取 {symbol} 涨跌幅失败: {e}") + return None + + def get_top_symbols(self) -> List[Dict]: + """ + 获取当前扫描到的前N个货币对 + + Returns: + 前N个货币对列表 + """ + return self.top_symbols + + async def monitor_price(self, symbol: str, callback) -> None: + """ + 监控单个交易对的价格变化(WebSocket) + + Args: + symbol: 交易对 + callback: 价格变化回调函数 + """ + try: + async with self.client.socket_manager.futures_socket(symbol.lower()) as stream: + async for msg in stream: + if 'data' in msg: + price = float(msg['data']['c']) # 最新价格 + await callback(symbol, price) + except Exception as e: + logger.error(f"监控 {symbol} 价格失败: {e}") diff --git a/position_manager.py b/position_manager.py new file mode 100644 index 0000000..b06050b --- /dev/null +++ b/position_manager.py @@ -0,0 +1,259 @@ +""" +仓位管理模块 - 管理持仓和订单 +""" +import logging +from typing import Dict, List, Optional +from binance_client import BinanceClient +from risk_manager import RiskManager +import config + +logger = logging.getLogger(__name__) + + +class PositionManager: + """仓位管理类""" + + def __init__(self, client: BinanceClient, risk_manager: RiskManager): + """ + 初始化仓位管理器 + + Args: + client: 币安客户端 + risk_manager: 风险管理器 + """ + self.client = client + self.risk_manager = risk_manager + self.active_positions: Dict[str, Dict] = {} + + async def open_position( + self, + symbol: str, + change_percent: float, + leverage: int = 10 + ) -> Optional[Dict]: + """ + 开仓 + + Args: + symbol: 交易对 + change_percent: 涨跌幅百分比 + leverage: 杠杆倍数 + + Returns: + 订单信息,失败返回None + """ + try: + # 判断是否应该交易 + if not await self.risk_manager.should_trade(symbol, change_percent): + return None + + # 设置杠杆 + await self.client.set_leverage(symbol, leverage) + + # 计算仓位大小 + quantity = await self.risk_manager.calculate_position_size( + symbol, change_percent + ) + + if quantity is None: + logger.warning(f"{symbol} 仓位计算失败,跳过交易") + return None + + # 确定交易方向 + side = 'BUY' if change_percent > 0 else 'SELL' + + # 获取当前价格 + ticker = await self.client.get_ticker_24h(symbol) + if not ticker: + return None + + entry_price = ticker['price'] + + # 下单 + order = await self.client.place_order( + symbol=symbol, + side=side, + quantity=quantity, + order_type='MARKET' + ) + + if order: + # 记录持仓信息 + position_info = { + 'symbol': symbol, + 'side': side, + 'quantity': quantity, + 'entryPrice': entry_price, + 'changePercent': change_percent, + 'orderId': order.get('orderId'), + 'stopLoss': self.risk_manager.get_stop_loss_price(entry_price, side), + 'takeProfit': self.risk_manager.get_take_profit_price(entry_price, side), + 'leverage': leverage + } + + self.active_positions[symbol] = position_info + + logger.info( + f"开仓成功: {symbol} {side} {quantity} @ {entry_price:.4f} " + f"(涨跌幅: {change_percent:.2f}%)" + ) + + return position_info + + return None + + except Exception as e: + logger.error(f"开仓失败 {symbol}: {e}") + return None + + async def close_position(self, symbol: str) -> bool: + """ + 平仓 + + Args: + symbol: 交易对 + + Returns: + 是否成功 + """ + try: + # 获取当前持仓 + positions = await self.client.get_open_positions() + position = next( + (p for p in positions if p['symbol'] == symbol), + None + ) + + if not position: + logger.warning(f"{symbol} 没有持仓") + return False + + # 确定平仓方向(与开仓相反) + position_amt = position['positionAmt'] + side = 'SELL' if position_amt > 0 else 'BUY' + quantity = abs(position_amt) + + # 平仓 + order = await self.client.place_order( + symbol=symbol, + side=side, + quantity=quantity, + order_type='MARKET' + ) + + if order: + # 移除持仓记录 + if symbol in self.active_positions: + del self.active_positions[symbol] + + logger.info(f"平仓成功: {symbol} {side} {quantity}") + return True + + return False + + except Exception as e: + logger.error(f"平仓失败 {symbol}: {e}") + return False + + async def check_stop_loss_take_profit(self) -> List[str]: + """ + 检查止损止盈 + + Returns: + 需要平仓的交易对列表 + """ + closed_positions = [] + + try: + # 获取当前持仓 + positions = await self.client.get_open_positions() + position_dict = {p['symbol']: p for p in positions} + + for symbol, position_info in list(self.active_positions.items()): + if symbol not in position_dict: + # 持仓已不存在,移除记录 + del self.active_positions[symbol] + continue + + current_position = position_dict[symbol] + entry_price = position_info['entryPrice'] + # 获取当前标记价格 + current_price = current_position.get('markPrice', 0) + if current_price == 0: + # 如果标记价格为0,尝试从ticker获取 + ticker = await self.client.get_ticker_24h(symbol) + if ticker: + current_price = ticker['price'] + else: + current_price = entry_price + + # 计算当前盈亏 + if position_info['side'] == 'BUY': + pnl_percent = ((current_price - entry_price) / entry_price) * 100 + else: + pnl_percent = ((entry_price - current_price) / entry_price) * 100 + + # 检查止损 + stop_loss = position_info['stopLoss'] + if position_info['side'] == 'BUY' and current_price <= stop_loss: + logger.warning(f"{symbol} 触发止损: {current_price:.4f} <= {stop_loss:.4f}") + if await self.close_position(symbol): + closed_positions.append(symbol) + continue + + if position_info['side'] == 'SELL' and current_price >= stop_loss: + logger.warning(f"{symbol} 触发止损: {current_price:.4f} >= {stop_loss:.4f}") + if await self.close_position(symbol): + closed_positions.append(symbol) + continue + + # 检查止盈 + take_profit = position_info['takeProfit'] + if position_info['side'] == 'BUY' and current_price >= take_profit: + logger.info(f"{symbol} 触发止盈: {current_price:.4f} >= {take_profit:.4f}") + if await self.close_position(symbol): + closed_positions.append(symbol) + continue + + if position_info['side'] == 'SELL' and current_price <= take_profit: + logger.info(f"{symbol} 触发止盈: {current_price:.4f} <= {take_profit:.4f}") + if await self.close_position(symbol): + closed_positions.append(symbol) + continue + + except Exception as e: + logger.error(f"检查止损止盈失败: {e}") + + return closed_positions + + async def get_position_summary(self) -> Dict: + """ + 获取持仓摘要 + + Returns: + 持仓摘要信息 + """ + try: + positions = await self.client.get_open_positions() + balance = await self.client.get_account_balance() + + total_pnl = sum(p['unRealizedProfit'] for p in positions) + + return { + 'totalPositions': len(positions), + 'totalBalance': balance.get('total', 0), + 'availableBalance': balance.get('available', 0), + 'totalPnL': total_pnl, + 'positions': [ + { + 'symbol': p['symbol'], + 'positionAmt': p['positionAmt'], + 'entryPrice': p['entryPrice'], + 'pnl': p['unRealizedProfit'] + } + for p in positions + ] + } + except Exception as e: + logger.error(f"获取持仓摘要失败: {e}") + return {} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..bf26138 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +python-binance==1.0.19 +websocket-client==1.6.1 +aiohttp==3.9.1 diff --git a/risk_manager.py b/risk_manager.py new file mode 100644 index 0000000..abd8f46 --- /dev/null +++ b/risk_manager.py @@ -0,0 +1,255 @@ +""" +风险管理模块 - 严格控制仓位和风险 +""" +import logging +from typing import Dict, List, Optional +from binance_client import BinanceClient +import config + +logger = logging.getLogger(__name__) + + +class RiskManager: + """风险管理类""" + + def __init__(self, client: BinanceClient): + """ + 初始化风险管理器 + + Args: + client: 币安客户端 + """ + self.client = client + self.config = config.TRADING_CONFIG + + async def check_position_size(self, symbol: str, quantity: float) -> bool: + """ + 检查单笔仓位大小是否符合要求 + + Args: + symbol: 交易对 + quantity: 下单数量 + + Returns: + 是否通过检查 + """ + try: + # 获取账户余额 + balance = await self.client.get_account_balance() + available_balance = balance.get('available', 0) + + if available_balance <= 0: + logger.warning("账户余额不足") + return False + + # 计算仓位价值(假设使用当前价格) + ticker = await self.client.get_ticker_24h(symbol) + if not ticker: + return False + + current_price = ticker['price'] + position_value = quantity * current_price + + # 检查单笔仓位是否超过最大限制 + max_position_value = available_balance * self.config['MAX_POSITION_PERCENT'] + min_position_value = available_balance * self.config['MIN_POSITION_PERCENT'] + + if position_value > max_position_value: + logger.warning( + f"{symbol} 仓位过大: {position_value:.2f} USDT > " + f"最大限制: {max_position_value:.2f} USDT" + ) + return False + + if position_value < min_position_value: + logger.warning( + f"{symbol} 仓位过小: {position_value:.2f} USDT < " + f"最小限制: {min_position_value:.2f} USDT" + ) + return False + + # 检查总仓位是否超过限制 + if not await self.check_total_position(position_value): + return False + + logger.info( + f"{symbol} 仓位检查通过: {position_value:.2f} USDT " + f"(账户余额: {available_balance:.2f} USDT)" + ) + return True + + except Exception as e: + logger.error(f"检查仓位大小失败: {e}") + return False + + async def check_total_position(self, new_position_value: float) -> bool: + """ + 检查总仓位是否超过限制 + + Args: + new_position_value: 新仓位价值 + + Returns: + 是否通过检查 + """ + try: + # 获取当前持仓 + positions = await self.client.get_open_positions() + + # 计算当前总仓位价值 + total_position_value = sum( + abs(pos['positionAmt'] * pos['entryPrice']) + for pos in positions + ) + + # 加上新仓位 + total_position_value += new_position_value + + # 获取账户余额 + balance = await self.client.get_account_balance() + total_balance = balance.get('total', 0) + + if total_balance <= 0: + return False + + max_total_position = total_balance * self.config['MAX_TOTAL_POSITION_PERCENT'] + + if total_position_value > max_total_position: + logger.warning( + f"总仓位超限: {total_position_value:.2f} USDT > " + f"最大限制: {max_total_position:.2f} USDT" + ) + return False + + logger.info( + f"总仓位检查通过: {total_position_value:.2f} USDT / " + f"最大限制: {max_total_position:.2f} USDT" + ) + return True + + except Exception as e: + logger.error(f"检查总仓位失败: {e}") + return False + + async def calculate_position_size( + self, + symbol: str, + change_percent: float + ) -> Optional[float]: + """ + 根据涨跌幅和风险参数计算合适的仓位大小 + + Args: + symbol: 交易对 + change_percent: 涨跌幅百分比 + + Returns: + 建议的仓位数量,如果不符合条件则返回None + """ + try: + # 获取账户余额 + balance = await self.client.get_account_balance() + available_balance = balance.get('available', 0) + + if available_balance <= 0: + logger.warning("账户余额不足") + return None + + # 获取当前价格 + ticker = await self.client.get_ticker_24h(symbol) + if not ticker: + return None + + current_price = ticker['price'] + + # 根据涨跌幅调整仓位大小(涨跌幅越大,仓位可以适当增加) + base_position_percent = self.config['MAX_POSITION_PERCENT'] + # 涨跌幅超过5%时,可以适当增加仓位(但不超过1.5倍) + if abs(change_percent) > 5: + position_percent = min( + base_position_percent * 1.5, + self.config['MAX_POSITION_PERCENT'] * 1.5 + ) + else: + position_percent = base_position_percent + + # 计算仓位价值 + position_value = available_balance * position_percent + + # 计算数量(考虑合约的最小数量精度) + quantity = position_value / current_price + + # 检查是否通过风险控制 + if await self.check_position_size(symbol, quantity): + return quantity + + return None + + except Exception as e: + logger.error(f"计算仓位大小失败: {e}") + return None + + async def should_trade(self, symbol: str, change_percent: float) -> bool: + """ + 判断是否应该交易 + + Args: + symbol: 交易对 + change_percent: 涨跌幅百分比 + + Returns: + 是否应该交易 + """ + # 检查最小涨跌幅阈值 + if abs(change_percent) < self.config['MIN_CHANGE_PERCENT']: + logger.debug(f"{symbol} 涨跌幅 {change_percent:.2f}% 小于阈值") + return False + + # 检查是否已有持仓 + positions = await self.client.get_open_positions() + existing_position = next( + (p for p in positions if p['symbol'] == symbol), + None + ) + + if existing_position: + logger.info(f"{symbol} 已有持仓,跳过") + return False + + return True + + def get_stop_loss_price(self, entry_price: float, side: str) -> float: + """ + 计算止损价格 + + Args: + entry_price: 入场价格 + side: 方向 'BUY' 或 'SELL' + + Returns: + 止损价格 + """ + stop_loss_percent = self.config['STOP_LOSS_PERCENT'] + + if side == 'BUY': # 做多,止损价低于入场价 + return entry_price * (1 - stop_loss_percent) + else: # 做空,止损价高于入场价 + return entry_price * (1 + stop_loss_percent) + + def get_take_profit_price(self, entry_price: float, side: str) -> float: + """ + 计算止盈价格 + + Args: + entry_price: 入场价格 + side: 方向 'BUY' 或 'SELL' + + Returns: + 止盈价格 + """ + take_profit_percent = self.config['TAKE_PROFIT_PERCENT'] + + if side == 'BUY': # 做多,止盈价高于入场价 + return entry_price * (1 + take_profit_percent) + else: # 做空,止盈价低于入场价 + return entry_price * (1 - take_profit_percent) diff --git a/strategy.py b/strategy.py new file mode 100644 index 0000000..9a7330e --- /dev/null +++ b/strategy.py @@ -0,0 +1,193 @@ +""" +交易策略模块 - 实现交易逻辑和优化 +""" +import asyncio +import logging +from typing import List, Dict, Optional +from binance_client import BinanceClient +from market_scanner import MarketScanner +from risk_manager import RiskManager +from position_manager import PositionManager +import config + +logger = logging.getLogger(__name__) + + +class TradingStrategy: + """交易策略类""" + + def __init__( + self, + client: BinanceClient, + scanner: MarketScanner, + risk_manager: RiskManager, + position_manager: PositionManager + ): + """ + 初始化交易策略 + + Args: + client: 币安客户端 + scanner: 市场扫描器 + risk_manager: 风险管理器 + position_manager: 仓位管理器 + """ + self.client = client + self.scanner = scanner + self.risk_manager = risk_manager + self.position_manager = position_manager + self.running = False + + async def execute_strategy(self): + """ + 执行交易策略 + """ + self.running = True + logger.info("交易策略开始执行...") + + try: + while self.running: + # 1. 扫描市场,找出涨跌幅最大的前N个货币对 + top_symbols = await self.scanner.scan_market() + + if not top_symbols: + logger.warning("未找到符合条件的交易对,等待下次扫描...") + await asyncio.sleep(config.TRADING_CONFIG['SCAN_INTERVAL']) + continue + + # 2. 对每个交易对执行交易逻辑 + for symbol_info in top_symbols: + if not self.running: + break + + symbol = symbol_info['symbol'] + change_percent = symbol_info['changePercent'] + direction = symbol_info['direction'] + + logger.info( + f"处理交易对: {symbol} " + f"({direction} {change_percent:.2f}%)" + ) + + # 检查是否应该交易 + if not await self.risk_manager.should_trade(symbol, change_percent): + continue + + # 优化:结合成交量确认 + if not await self._check_volume_confirmation(symbol_info): + logger.info(f"{symbol} 成交量确认失败,跳过") + continue + + # 优化:趋势确认(可选) + if not await self._check_trend_confirmation(symbol, change_percent): + logger.info(f"{symbol} 趋势确认失败,跳过") + continue + + # 开仓 + position = await self.position_manager.open_position( + symbol=symbol, + change_percent=change_percent, + leverage=10 # 默认10倍杠杆 + ) + + if position: + logger.info(f"{symbol} 开仓成功") + else: + logger.warning(f"{symbol} 开仓失败") + + # 避免同时处理太多交易对 + await asyncio.sleep(1) + + # 3. 检查止损止盈 + await self.position_manager.check_stop_loss_take_profit() + + # 4. 打印持仓摘要 + summary = await self.position_manager.get_position_summary() + if summary: + logger.info( + f"持仓摘要: {summary['totalPositions']} 个持仓, " + f"总盈亏: {summary['totalPnL']:.2f} USDT, " + f"可用余额: {summary['availableBalance']:.2f} USDT" + ) + + # 等待下次扫描 + logger.info(f"等待 {config.TRADING_CONFIG['SCAN_INTERVAL']} 秒后进行下次扫描...") + await asyncio.sleep(config.TRADING_CONFIG['SCAN_INTERVAL']) + + except Exception as e: + logger.error(f"策略执行出错: {e}", exc_info=True) + finally: + self.running = False + logger.info("交易策略已停止") + + async def _check_volume_confirmation(self, symbol_info: Dict) -> bool: + """ + 成交量确认 - 确保有足够的成交量支撑 + + Args: + symbol_info: 交易对信息 + + Returns: + 是否通过确认 + """ + volume_24h = symbol_info.get('volume24h', 0) + min_volume = config.TRADING_CONFIG['MIN_VOLUME_24H'] + + if volume_24h < min_volume: + return False + + return True + + async def _check_trend_confirmation( + self, + symbol: str, + change_percent: float + ) -> bool: + """ + 趋势确认 - 结合多时间周期确认趋势 + + Args: + symbol: 交易对 + change_percent: 5分钟涨跌幅 + + Returns: + 是否通过确认 + """ + try: + # 获取15分钟K线数据,确认更大周期的趋势 + klines_15m = await self.client.get_klines( + symbol=symbol, + interval='15m', + limit=2 + ) + + if len(klines_15m) < 2: + return True # 如果无法获取,默认通过 + + # 计算15分钟涨跌幅 + current_price_15m = float(klines_15m[-1][4]) + prev_price_15m = float(klines_15m[-2][4]) + + if prev_price_15m == 0: + return True + + change_15m = ((current_price_15m - prev_price_15m) / prev_price_15m) * 100 + + # 5分钟和15分钟趋势一致时,确认通过 + if (change_percent > 0 and change_15m > 0) or (change_percent < 0 and change_15m < 0): + return True + + # 如果5分钟涨跌幅很大(>5%),即使15分钟不一致也允许 + if abs(change_percent) > 5: + return True + + return False + + except Exception as e: + logger.debug(f"趋势确认失败 {symbol}: {e}") + return True # 出错时默认通过 + + def stop(self): + """停止策略""" + self.running = False + logger.info("正在停止交易策略...")