This commit is contained in:
薇薇安 2026-01-13 14:30:57 +08:00
parent c5279db8d3
commit 5c841621f7
11 changed files with 1746 additions and 1 deletions

173
README.md
View File

@ -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初始版本实现基础自动交易功能

302
binance_client.py Normal file
View File

@ -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

45
config.example.py Normal file
View File

@ -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'

44
config.py Normal file
View File

@ -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'

View File

@ -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
```

105
main.py Normal file
View File

@ -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)

152
market_scanner.py Normal file
View File

@ -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}")

259
position_manager.py Normal file
View File

@ -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 {}

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
python-binance==1.0.19
websocket-client==1.6.1
aiohttp==3.9.1

255
risk_manager.py Normal file
View File

@ -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)

193
strategy.py Normal file
View File

@ -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("正在停止交易策略...")