添加平仓条件

This commit is contained in:
yhydev
2026-02-02 11:35:02 +08:00
parent 77887be4fb
commit b1d4245db7

View File

@@ -1,15 +1,18 @@
"""
币安永续合约定时平仓脚本。
平仓前置条件(不满足则直接退出,不执行平仓):
- 当前总未实现盈亏 - 近 N 分钟最小总未实现盈亏 > 配置阈值(默认 2 USDT未实现盈亏历史存 Redis Sorted Set。
平仓触发条件(满足其一即平仓):
- 在 Redis 指定 Set 中的合约强制平仓;
- 小仓位:多空名义价值总和 < 30 USDT 且未实现盈亏 > 0.03
- 大仓位:多空名义价值总和 > 50 USDT 且未实现盈亏 > 0.3。
流程:
1. 获取当前持仓
2. 得到需要平仓的交易对;
3. 创建 BinanceSocketManager监听需平仓合约交易对,收到 WS 事件后按规则创建平仓订单。
1. 获取当前总未实现盈亏,写入 Redis 并取近 N 分钟最小;若不满足「当前 - 最小 > 阈值」则退出
2. 获取当前持仓,得到需要平仓的交易对;
3. 创建 BinanceSocketManager监听需平仓合约收到 WS 事件后按规则创建平仓订单。
平仓规则:多头限价卖出 = 当前价 * 1.003,空头限价买入 = 当前价 * 0.997。
纯逻辑为顶层函数便于单元测试from position_closer import round_to_step, ...
@@ -246,6 +249,16 @@ async def get_symbol_precision(client: BinanceAsyncClient) -> dict[str, dict]:
return parse_exchange_info_to_precisions(info)
async def get_total_unrealized_profit(client: BinanceAsyncClient) -> float:
"""获取账户永续合约总未实现盈亏(所有持仓的 unrealizedProfit 之和)。"""
account = await client.futures_account()
positions = account.get("positions", [])
total = 0.0
for p in positions:
total += float(p.get("unrealizedProfit", 0) or 0)
return total
async def get_positions(client: BinanceAsyncClient) -> list[dict]:
"""获取永续合约持仓(只保留有仓位的)。"""
account = await client.futures_account()
@@ -253,6 +266,65 @@ async def get_positions(client: BinanceAsyncClient) -> list[dict]:
return filter_nonzero_positions(positions)
async def save_unrealized_profit_history(
redis_url: str,
history_key: str,
profit: float,
window_seconds: float,
) -> None:
"""
将当前总未实现盈亏写入 Redis Sorted Setscore=时间戳, member=时间戳:盈亏值),
并删除早于 (now - window_seconds) 的旧数据。
"""
import time
from redis.asyncio import Redis as Aioredis
now = time.time()
member = f"{now}:{profit}"
redis_client = Aioredis.from_url(redis_url, decode_responses=True)
try:
await redis_client.zadd(history_key, {member: now})
await redis_client.zremrangebyscore(history_key, "-inf", f"({now - window_seconds}")
finally:
await redis_client.aclose()
async def get_min_unrealized_profit_in_window(
redis_url: str,
history_key: str,
window_seconds: float,
) -> float | None:
"""
从 Redis Sorted Set 中取近 window_seconds 秒内记录的最小未实现盈亏。
无数据或解析失败时返回 None。
"""
import time
from redis.asyncio import Redis as Aioredis
now = time.time()
min_score = now - window_seconds
redis_client = Aioredis.from_url(redis_url, decode_responses=True)
try:
members = await redis_client.zrangebyscore(
history_key, min_score, now, start=0, num=-1
)
finally:
await redis_client.aclose()
if not members:
return None
values: list[float] = []
for m in members:
# member 格式为 "timestamp:profit"
parts = str(m).split(":", 1)
if len(parts) == 2:
try:
values.append(float(parts[1]))
except ValueError:
continue
return min(values) if values else None
async def place_close_order(
client: BinanceAsyncClient,
symbol: str,
@@ -431,12 +503,55 @@ async def main_async() -> None:
large_min_profit = float(getattr(settings, "notional_large_close_min_profit", None) or 0.3)
redis_key = getattr(settings, "redis_close_key", None) or "close_position:contracts"
ws_connection_timeout = float(getattr(settings, "ws_connection_timeout", None) or 30)
redis_profit_history_key = (
getattr(settings, "redis_unrealized_profit_history_key", None)
or "close_position:unrealized_profit_history"
)
profit_window_seconds = float(
getattr(settings, "unrealized_profit_window_seconds", None) or 300
)
profit_min_rise = float(
getattr(settings, "unrealized_profit_min_rise", None) or 2
)
client = await BinanceAsyncClient.create(
api_key=api_key,
api_secret=api_secret
)
try:
total_profit = await get_total_unrealized_profit(client)
await save_unrealized_profit_history(
redis_url, redis_profit_history_key, total_profit, profit_window_seconds
)
min_profit = await get_min_unrealized_profit_in_window(
redis_url, redis_profit_history_key, profit_window_seconds
)
if min_profit is None:
logger.info(
"{} 秒内无未实现盈亏历史,本次不执行平仓(已记录当前总未实现盈亏 {:.2f}",
profit_window_seconds,
total_profit,
)
return
if total_profit - min_profit <= profit_min_rise:
logger.info(
"未满足平仓前置条件:当前总未实现盈亏 {:.2f} - 近 {} 秒最小 {:.2f} = {:.2f} <= {},退出",
total_profit,
profit_window_seconds,
min_profit,
total_profit - min_profit,
profit_min_rise,
)
return
logger.info(
"满足平仓前置条件:当前总未实现盈亏 {:.2f},近 {} 秒最小 {:.2f},差值 {:.2f} > {}",
total_profit,
profit_window_seconds,
min_profit,
total_profit - min_profit,
profit_min_rise,
)
positions = await get_positions(client)
by_symbol = group_positions_by_symbol(positions)