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>
97 lines
3.6 KiB
Python
97 lines
3.6 KiB
Python
"""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()
|