Files
Chan/research/live/probe_rules.py
T
jackandCursor c57adc8cb0 推送按合约规则取整,减半腿精确一半
100 USDT 的仓位在 Bitget 上不是最小下单量的问题(minTradeUSDT=5、
minTradeNum 极小,10U 都能下),是**步长取整**的问题:

  SOL  步长 0.1 币 ≈ 10.7U → 50U 腿只能取 0.4 币 = 42.7U,偏 14.6%
  LINK 步长 1 币 ≈ 11.7U   → 4 币 = 46.8U,偏 6.4%
  BTC  步长 0.0001 ≈ 8.0U  → 0.0006 = 47.8U,偏 4.4%

减半腿变成全仓的 43% 而非 50%,剩下 57% 暴露在 8ATR 目标上,而回测的收益
结构假设 50/50。小额下这不影响机制验证,但会让 P&L 读不出回测那个结构。

解法是入场量取到**步长的偶数倍**,这样一半天然落在步长上。实测十个币减半腿
全部精确 50%,名义额落在 93.6~106.7 USDT——小额实盘无所谓。

同时把价位对齐到 tick(priceEndStep × 10^-pricePlace),否则限价单会被拒。
规则拉一次缓存;拉不到就退化为不取整并打日志,不阻断推送。

费率一事已核实无需改动:预算假设的挂牌 taker 0.040% / maker 0.016% 正是
Bitget VIP2 的官方档(返 50% 后 2.0 / 0.8bp)。合约接口返的 6bp/2bp 是 VIP0
基础档,不适用。8bp 门控阈值不变。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 15:42:17 +08:00

97 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""100 USDT 的仓位在各币上能不能下出来——下单精度与最小量。
手工小额实盘的第一个坑不在策略,在交易规则。100 USDT 的仓位要拆成两半
3 ATR 减半 50 USDT、8 ATR 目标 50 USDT),任一半低于最小下单量就下不出去,
或者被精度取整到与计划偏差很大的数量。
取整偏差会直接扭曲收益结构:若 50 USDT 被取整到 40,减半那一腿实际只出了
40%,剩余 60% 暴露在 8 ATR 目标上。回测的收益结构假设是 50/50。
python research/live/probe_rules.py --notional 100
"""
from __future__ import annotations
import argparse
import asyncio
import os
import sys
from decimal import Decimal
from pathlib import Path
HERE = Path(__file__).resolve()
sys.path.insert(0, str(HERE.parents[1]))
sys.path.insert(0, str(HERE.parent))
SYMS = os.environ.get(
"SYMS", "BTC,ETH,SOL,BNB,XRP,DOGE,ADA,AVAX,LINK,LTC").split(",")
async def run(notional: float) -> None:
from hummingbot.connector.derivative.bitget_perpetual.bitget_perpetual_derivative import ( # noqa: E501
BitgetPerpetualDerivative,
)
pairs = [f"{s}-USDT" for s in SYMS]
conn = BitgetPerpetualDerivative(
bitget_perpetual_api_key="", bitget_perpetual_secret_key="",
bitget_perpetual_passphrase="", trading_pairs=pairs,
trading_required=False)
await conn.start_network()
for _ in range(60):
await asyncio.sleep(1)
if conn.trading_rules and all(p in conn.trading_rules for p in pairs):
break
print(f"仓位 {notional:.0f} USDT · 减半腿 {notional / 2:.0f} USDT\n")
print(f" {'币':<6}{'现价':>11}{'最小量':>12}{'量步长':>12}"
f"{'最小名义':>10} 减半腿可行性")
bad = []
for s in SYMS:
p = f"{s}-USDT"
r = conn.trading_rules.get(p)
if r is None:
print(f" {s:<6}{'取不到规则':>11}")
continue
ob = conn.get_order_book(p)
px = float((ob.get_price(True) + ob.get_price(False)) / 2) if ob \
else float("nan")
min_amt = float(r.min_order_size)
step = float(r.min_base_amount_increment)
min_not = float(r.min_notional_size or 0)
half_base = (notional / 2) / px
# 按步长向下取整——交易所就是这么处理的,向上取会下不出去
q = Decimal(str(half_base)) // Decimal(str(step)) * Decimal(str(step))
got = float(q)
if got < min_amt or (min_not and got * px < min_not):
verdict = f"⛔ 下不出(需 ≥ {max(min_amt, min_not / px):.6f}"
bad.append(s)
else:
dev = abs(got * px - notional / 2) / (notional / 2) * 1e4
verdict = f"✓ {got:.6f} 币,偏差 {dev:.0f}bp"
if dev > 100:
verdict += " ⚠ 取整偏差大"
bad.append(s)
print(f" {s:<6}{px:>11,.4f}{min_amt:>12.6f}{step:>12.6f}"
f"{min_not:>10.1f} {verdict}")
print()
if bad:
print(f" ⛔ {notional:.0f} USDT 下这些币的减半腿有问题:{','.join(bad)}")
print(f" 要么提高仓位,要么这些币不做减半、单腿到 8 ATR 出场——但后者")
print(f" 改了回测的收益结构,不能直接套用原预算。")
else:
print(f" {notional:.0f} USDT 在全部 {len(SYMS)} 个币上都能拆成两半下出。")
await conn.stop_network()
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--notional", type=float, default=100.0)
a = ap.parse_args()
asyncio.run(run(a.notional))
if __name__ == "__main__":
main()