From 324ed50b9215f1c8ec8413015fb2412456e04b70 Mon Sep 17 00:00:00 2001 From: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Date: Tue, 4 Nov 2025 19:07:58 +0800 Subject: [PATCH] =?UTF-8?q?fix(binance):=20initialize=20dual-side=20positi?= =?UTF-8?q?on=20mode=20to=20prevent=20code=3D-4061=20errors=20##=20Problem?= =?UTF-8?q?=20When=20opening=20positions=20with=20explicit=20`PositionSide?= =?UTF-8?q?`=20parameter=20(LONG/SHORT),=20Binance=20API=20returned=20**co?= =?UTF-8?q?de=3D-4061**=20error:=20```=20"No=20need=20to=20change=20positi?= =?UTF-8?q?on=20side."=20"code":-4061=20```=20**Root=20cause:**=20-=20Bina?= =?UTF-8?q?nce=20accounts=20default=20to=20**single-side=20position=20mode?= =?UTF-8?q?**=20("One-Way=20Mode")=20-=20In=20this=20mode,=20`PositionSide?= =?UTF-8?q?`=20parameter=20is=20**not=20allowed**=20-=20Code=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E4=BA=86=20`PositionSide`=20=E5=8F=83=E6=95=B8=20(LON?= =?UTF-8?q?G/SHORT)=EF=BC=8C=E4=BD=86=E5=B8=B3=E6=88=B6=E6=9C=AA=E5=95=9F?= =?UTF-8?q?=E7=94=A8=E9=9B=99=E5=90=91=E6=8C=81=E5=80=89=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=20**Position=20Mode=20Comparison:**=20|=20Mode=20|=20PositionS?= =?UTF-8?q?ide=20Required=20|=20Can=20Hold=20Long+Short=20Simultaneously?= =?UTF-8?q?=20|=20|------|----------------------|-------------------------?= =?UTF-8?q?-----------|=20|=20One-Way=20(default)=20|=20=E2=9D=8C=20No=20|?= =?UTF-8?q?=20=E2=9D=8C=20No=20|=20|=20Hedge=20Mode=20|=20=E2=9C=85=20**Re?= =?UTF-8?q?quired**=20|=20=E2=9C=85=20Yes=20|=20##=20Solution=20###=201.?= =?UTF-8?q?=20Added=20setDualSidePosition()=20function=20Automatically=20e?= =?UTF-8?q?nables=20Hedge=20Mode=20during=20trader=20initialization:=20```?= =?UTF-8?q?go=20func=20(t=20*FuturesTrader)=20setDualSidePosition()=20erro?= =?UTF-8?q?r=20{=20=20=20=20=20err=20:=3D=20t.client.NewChangePositionMode?= =?UTF-8?q?Service().=20=20=20=20=20=20=20=20=20DualSide(true).=20//=20Ena?= =?UTF-8?q?ble=20Hedge=20Mode=20=20=20=20=20=20=20=20=20Do(context.Backgro?= =?UTF-8?q?und())=20=20=20=20=20if=20err=20!=3D=20nil=20{=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20//=20Ignore=20"No=20need=20to=20change"=20error=20(?= =?UTF-8?q?already=20in=20Hedge=20Mode)=20=20=20=20=20=20=20=20=20if=20str?= =?UTF-8?q?ings.Contains(err.Error(),=20"No=20need=20to=20change=20positio?= =?UTF-8?q?n=20side")=20{=20=20=20=20=20=20=20=20=20=20=20=20=20log.Printf?= =?UTF-8?q?("=E2=9C=93=20Account=20already=20in=20Hedge=20Mode")=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20return=20nil=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20}=20=20=20=20=20=20=20=20=20return=20err=20=20=20=20=20}?= =?UTF-8?q?=20=20=20=20=20log.Printf("=E2=9C=93=20Switched=20to=20Hedge=20?= =?UTF-8?q?Mode")=20=20=20=20=20return=20nil=20}=20```=20###=202.=20Called?= =?UTF-8?q?=20in=20NewFuturesTrader()=20Runs=20automatically=20when=20crea?= =?UTF-8?q?ting=20trader=20instance:=20```go=20func=20NewFuturesTrader(api?= =?UTF-8?q?Key,=20secretKey=20string)=20*FuturesTrader=20{=20=20=20=20=20t?= =?UTF-8?q?rader=20:=3D=20&FuturesTrader{...}=20=20=20=20=20//=20Initializ?= =?UTF-8?q?e=20Hedge=20Mode=20=20=20=20=20if=20err=20:=3D=20trader.setDual?= =?UTF-8?q?SidePosition();=20err=20!=3D=20nil=20{=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20log.Printf("=E2=9A=A0=EF=B8=8F=20Failed=20to=20set=20Hedge?= =?UTF-8?q?=20Mode:=20%v",=20err)=20=20=20=20=20}=20=20=20=20=20return=20t?= =?UTF-8?q?rader=20}=20```=20##=20Impact=20-=20=E2=9C=85=20Prevents=20code?= =?UTF-8?q?=3D-4061=20errors=20when=20opening=20positions=20-=20=E2=9C=85?= =?UTF-8?q?=20Enables=20simultaneous=20long+short=20positions=20(if=20need?= =?UTF-8?q?ed)=20-=20=E2=9C=85=20Fails=20gracefully=20if=20account=20alrea?= =?UTF-8?q?dy=20in=20Hedge=20Mode=20-=20=E2=9A=A0=EF=B8=8F=20**One-time=20?= =?UTF-8?q?change**:=20Once=20enabled,=20cannot=20revert=20to=20One-Way=20?= =?UTF-8?q?Mode=20with=20open=20positions=20##=20Testing=20-=20=E2=9C=85?= =?UTF-8?q?=20Compiles=20successfully=20-=20=E2=9A=A0=EF=B8=8F=20Requires?= =?UTF-8?q?=20Binance=20testnet/mainnet=20validation:=20=20=20-=20[=20]=20?= =?UTF-8?q?First=20initialization=20=E2=86=92=20switches=20to=20Hedge=20Mo?= =?UTF-8?q?de=20=20=20-=20[=20]=20Subsequent=20initializations=20=E2=86=92?= =?UTF-8?q?=20ignores=20"No=20need=20to=20change"=20error=20=20=20-=20[=20?= =?UTF-8?q?]=20Open=20long=20position=20with=20PositionSide=3DLONG=20?= =?UTF-8?q?=E2=86=92=20succeeds=20=20=20-=20[=20]=20Open=20short=20positio?= =?UTF-8?q?n=20with=20PositionSide=3DSHORT=20=E2=86=92=20succeeds=20##=20C?= =?UTF-8?q?ode=20Changes=20```=20trader/binance=5Ffutures.go:=20-=20Line?= =?UTF-8?q?=203-12:=20Added=20strings=20import=20-=20Line=2033-47:=20Modif?= =?UTF-8?q?ied=20NewFuturesTrader()=20to=20call=20setDualSidePosition()=20?= =?UTF-8?q?-=20Line=2049-69:=20New=20function=20setDualSidePosition()=20To?= =?UTF-8?q?tal:=20+25=20lines=20```=20##=20References=20-=20Binance=20Futu?= =?UTF-8?q?res=20API:=20https://binance-docs.github.io/apidocs/futures/en/?= =?UTF-8?q?#change-position-mode-trade=20-=20Error=20code=3D-4061:=20"No?= =?UTF-8?q?=20need=20to=20change=20position=20side."=20-=20PositionSide=20?= =?UTF-8?q?ENUM:=20BOTH=20(One-Way)=20|=20LONG=20|=20SHORT=20(Hedge=20Mode?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- trader/binance_futures.go | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/trader/binance_futures.go b/trader/binance_futures.go index 354415a0..5cffd96c 100644 --- a/trader/binance_futures.go +++ b/trader/binance_futures.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "strconv" + "strings" "sync" "time" @@ -32,10 +33,40 @@ type FuturesTrader struct { // NewFuturesTrader 创建合约交易器 func NewFuturesTrader(apiKey, secretKey string) *FuturesTrader { client := futures.NewClient(apiKey, secretKey) - return &FuturesTrader{ + trader := &FuturesTrader{ client: client, cacheDuration: 15 * time.Second, // 15秒缓存 } + + // 设置双向持仓模式(Hedge Mode) + // 这是必需的,因为代码中使用了 PositionSide (LONG/SHORT) + if err := trader.setDualSidePosition(); err != nil { + log.Printf("⚠️ 设置双向持仓模式失败: %v (如果已是双向模式则忽略此警告)", err) + } + + return trader +} + +// setDualSidePosition 设置双向持仓模式(初始化时调用) +func (t *FuturesTrader) setDualSidePosition() error { + // 尝试设置双向持仓模式 + err := t.client.NewChangePositionModeService(). + DualSide(true). // true = 双向持仓(Hedge Mode) + Do(context.Background()) + + if err != nil { + // 如果错误信息包含"No need to change",说明已经是双向持仓模式 + if strings.Contains(err.Error(), "No need to change position side") { + log.Printf(" ✓ 账户已是双向持仓模式(Hedge Mode)") + return nil + } + // 其他错误则返回(但在调用方不会中断初始化) + return err + } + + log.Printf(" ✓ 账户已切换为双向持仓模式(Hedge Mode)") + log.Printf(" ℹ️ 双向持仓模式允许同时持有多单和空单") + return nil } // GetBalance 获取账户余额(带缓存)