a
This commit is contained in:
parent
c5279db8d3
commit
5c841621f7
173
README.md
173
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:初始版本,实现基础自动交易功能
|
||||||
|
|
||||||
|
|
|
||||||
302
binance_client.py
Normal file
302
binance_client.py
Normal 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
45
config.example.py
Normal 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
44
config.py
Normal 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'
|
||||||
216
cursor_binance_automated_trading_system.md
Normal file
216
cursor_binance_automated_trading_system.md
Normal 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
105
main.py
Normal 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
152
market_scanner.py
Normal 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
259
position_manager.py
Normal 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
3
requirements.txt
Normal 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
255
risk_manager.py
Normal 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
193
strategy.py
Normal 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("正在停止交易策略...")
|
||||||
Loading…
Reference in New Issue
Block a user