260 lines
8.9 KiB
Python
260 lines
8.9 KiB
Python
"""
|
||
仓位管理模块 - 管理持仓和订单
|
||
"""
|
||
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 {}
|