Skip to content

Commit 70fa073

Browse files
authored
Merge pull request #434 from zhouyongyou/fix/stop-loss-take-profit-separation
fix(trader): separate stop-loss and take-profit order cancellation to prevent accidental deletions
2 parents c5d865f + 7e8216a commit 70fa073

File tree

4 files changed

+346
-0
lines changed

4 files changed

+346
-0
lines changed

trader/aster_trader.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,161 @@ func (t *AsterTrader) SetTakeProfit(symbol string, positionSide string, quantity
995995
return err
996996
}
997997

998+
// CancelStopOrders 取消该币种的止盈/止损单(用于调整止盈止损位置)
999+
func (t *AsterTrader) CancelStopOrders(symbol string) error {
1000+
// 获取该币种的所有未完成订单
1001+
params := map[string]interface{}{
1002+
"symbol": symbol,
1003+
}
1004+
1005+
body, err := t.request("GET", "/fapi/v3/openOrders", params)
1006+
if err != nil {
1007+
return fmt.Errorf("获取未完成订单失败: %w", err)
1008+
}
1009+
1010+
var orders []map[string]interface{}
1011+
if err := json.Unmarshal(body, &orders); err != nil {
1012+
return fmt.Errorf("解析订单数据失败: %w", err)
1013+
}
1014+
1015+
// 过滤出止盈止损单并取消
1016+
canceledCount := 0
1017+
for _, order := range orders {
1018+
orderType, _ := order["type"].(string)
1019+
1020+
// 只取消止损和止盈订单
1021+
if orderType == "STOP_MARKET" ||
1022+
orderType == "TAKE_PROFIT_MARKET" ||
1023+
orderType == "STOP" ||
1024+
orderType == "TAKE_PROFIT" {
1025+
1026+
orderID, _ := order["orderId"].(float64)
1027+
cancelParams := map[string]interface{}{
1028+
"symbol": symbol,
1029+
"orderId": int64(orderID),
1030+
}
1031+
1032+
_, err := t.request("DELETE", "/fapi/v3/order", cancelParams)
1033+
if err != nil {
1034+
log.Printf(" ⚠ 取消订单 %d 失败: %v", int64(orderID), err)
1035+
continue
1036+
}
1037+
1038+
canceledCount++
1039+
log.Printf(" ✓ 已取消 %s 的止盈/止损单 (订单ID: %d, 类型: %s)",
1040+
symbol, int64(orderID), orderType)
1041+
}
1042+
}
1043+
1044+
if canceledCount == 0 {
1045+
log.Printf(" ℹ %s 没有止盈/止损单需要取消", symbol)
1046+
} else {
1047+
log.Printf(" ✓ 已取消 %s 的 %d 个止盈/止损单", symbol, canceledCount)
1048+
}
1049+
1050+
return nil
1051+
}
1052+
1053+
// CancelStopLossOrders 仅取消止损单(不影响止盈单)
1054+
func (t *AsterTrader) CancelStopLossOrders(symbol string) error {
1055+
// 获取该币种的所有未完成订单
1056+
params := map[string]interface{}{
1057+
"symbol": symbol,
1058+
}
1059+
1060+
body, err := t.request("GET", "/fapi/v3/openOrders", params)
1061+
if err != nil {
1062+
return fmt.Errorf("获取未完成订单失败: %w", err)
1063+
}
1064+
1065+
var orders []map[string]interface{}
1066+
if err := json.Unmarshal(body, &orders); err != nil {
1067+
return fmt.Errorf("解析订单数据失败: %w", err)
1068+
}
1069+
1070+
// 过滤出止损单并取消
1071+
canceledCount := 0
1072+
for _, order := range orders {
1073+
orderType, _ := order["type"].(string)
1074+
1075+
// 只取消止损订单(不取消止盈订单)
1076+
if orderType == "STOP_MARKET" || orderType == "STOP" {
1077+
orderID, _ := order["orderId"].(float64)
1078+
cancelParams := map[string]interface{}{
1079+
"symbol": symbol,
1080+
"orderId": int64(orderID),
1081+
}
1082+
1083+
_, err := t.request("DELETE", "/fapi/v1/order", cancelParams)
1084+
if err != nil {
1085+
log.Printf(" ⚠ 取消止损单 %d 失败: %v", int64(orderID), err)
1086+
continue
1087+
}
1088+
1089+
canceledCount++
1090+
log.Printf(" ✓ 已取消止损单 (订单ID: %d, 类型: %s)", int64(orderID), orderType)
1091+
}
1092+
}
1093+
1094+
if canceledCount == 0 {
1095+
log.Printf(" ℹ %s 没有止损单需要取消", symbol)
1096+
} else {
1097+
log.Printf(" ✓ 已取消 %s 的 %d 个止损单", symbol, canceledCount)
1098+
}
1099+
1100+
return nil
1101+
}
1102+
1103+
// CancelTakeProfitOrders 仅取消止盈单(不影响止损单)
1104+
func (t *AsterTrader) CancelTakeProfitOrders(symbol string) error {
1105+
// 获取该币种的所有未完成订单
1106+
params := map[string]interface{}{
1107+
"symbol": symbol,
1108+
}
1109+
1110+
body, err := t.request("GET", "/fapi/v3/openOrders", params)
1111+
if err != nil {
1112+
return fmt.Errorf("获取未完成订单失败: %w", err)
1113+
}
1114+
1115+
var orders []map[string]interface{}
1116+
if err := json.Unmarshal(body, &orders); err != nil {
1117+
return fmt.Errorf("解析订单数据失败: %w", err)
1118+
}
1119+
1120+
// 过滤出止盈单并取消
1121+
canceledCount := 0
1122+
for _, order := range orders {
1123+
orderType, _ := order["type"].(string)
1124+
1125+
// 只取消止盈订单(不取消止损订单)
1126+
if orderType == "TAKE_PROFIT_MARKET" || orderType == "TAKE_PROFIT" {
1127+
orderID, _ := order["orderId"].(float64)
1128+
cancelParams := map[string]interface{}{
1129+
"symbol": symbol,
1130+
"orderId": int64(orderID),
1131+
}
1132+
1133+
_, err := t.request("DELETE", "/fapi/v1/order", cancelParams)
1134+
if err != nil {
1135+
log.Printf(" ⚠ 取消止盈单 %d 失败: %v", int64(orderID), err)
1136+
continue
1137+
}
1138+
1139+
canceledCount++
1140+
log.Printf(" ✓ 已取消止盈单 (订单ID: %d, 类型: %s)", int64(orderID), orderType)
1141+
}
1142+
}
1143+
1144+
if canceledCount == 0 {
1145+
log.Printf(" ℹ %s 没有止盈单需要取消", symbol)
1146+
} else {
1147+
log.Printf(" ✓ 已取消 %s 的 %d 个止盈单", symbol, canceledCount)
1148+
}
1149+
1150+
return nil
1151+
}
1152+
9981153
// CancelAllOrders 取消所有订单
9991154
func (t *AsterTrader) CancelAllOrders(symbol string) error {
10001155
params := map[string]interface{}{

trader/binance_futures.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,137 @@ func (t *FuturesTrader) CloseShort(symbol string, quantity float64) (map[string]
411411
return result, nil
412412
}
413413

414+
// CancelStopOrders 取消该币种的止盈/止损单(已废弃:会同时删除止损和止盈)
415+
func (t *FuturesTrader) CancelStopOrders(symbol string) error {
416+
// 获取该币种的所有未完成订单
417+
orders, err := t.client.NewListOpenOrdersService().
418+
Symbol(symbol).
419+
Do(context.Background())
420+
421+
if err != nil {
422+
return fmt.Errorf("获取未完成订单失败: %w", err)
423+
}
424+
425+
// 过滤出止盈止损单并取消
426+
canceledCount := 0
427+
for _, order := range orders {
428+
orderType := order.Type
429+
430+
// 只取消止损和止盈订单
431+
if orderType == futures.OrderTypeStopMarket ||
432+
orderType == futures.OrderTypeTakeProfitMarket ||
433+
orderType == futures.OrderTypeStop ||
434+
orderType == futures.OrderTypeTakeProfit {
435+
436+
_, err := t.client.NewCancelOrderService().
437+
Symbol(symbol).
438+
OrderID(order.OrderID).
439+
Do(context.Background())
440+
441+
if err != nil {
442+
log.Printf(" ⚠ 取消订单 %d 失败: %v", order.OrderID, err)
443+
continue
444+
}
445+
446+
canceledCount++
447+
log.Printf(" ✓ 已取消 %s 的止盈/止损单 (订单ID: %d, 类型: %s)",
448+
symbol, order.OrderID, orderType)
449+
}
450+
}
451+
452+
if canceledCount == 0 {
453+
log.Printf(" ℹ %s 没有止盈/止损单需要取消", symbol)
454+
} else {
455+
log.Printf(" ✓ 已取消 %s 的 %d 个止盈/止损单", symbol, canceledCount)
456+
}
457+
458+
return nil
459+
}
460+
461+
// CancelStopLossOrders 仅取消止损单(不影响止盈单)
462+
func (t *FuturesTrader) CancelStopLossOrders(symbol string) error {
463+
// 获取该币种的所有未完成订单
464+
orders, err := t.client.NewListOpenOrdersService().
465+
Symbol(symbol).
466+
Do(context.Background())
467+
468+
if err != nil {
469+
return fmt.Errorf("获取未完成订单失败: %w", err)
470+
}
471+
472+
// 过滤出止损单并取消
473+
canceledCount := 0
474+
for _, order := range orders {
475+
orderType := order.Type
476+
477+
// 只取消止损订单(不取消止盈订单)
478+
if orderType == futures.OrderTypeStopMarket || orderType == futures.OrderTypeStop {
479+
_, err := t.client.NewCancelOrderService().
480+
Symbol(symbol).
481+
OrderID(order.OrderID).
482+
Do(context.Background())
483+
484+
if err != nil {
485+
log.Printf(" ⚠ 取消止损单 %d 失败: %v", order.OrderID, err)
486+
continue
487+
}
488+
489+
canceledCount++
490+
log.Printf(" ✓ 已取消止损单 (订单ID: %d, 类型: %s)", order.OrderID, orderType)
491+
}
492+
}
493+
494+
if canceledCount == 0 {
495+
log.Printf(" ℹ %s 没有止损单需要取消", symbol)
496+
} else {
497+
log.Printf(" ✓ 已取消 %s 的 %d 个止损单", symbol, canceledCount)
498+
}
499+
500+
return nil
501+
}
502+
503+
// CancelTakeProfitOrders 仅取消止盈单(不影响止损单)
504+
func (t *FuturesTrader) CancelTakeProfitOrders(symbol string) error {
505+
// 获取该币种的所有未完成订单
506+
orders, err := t.client.NewListOpenOrdersService().
507+
Symbol(symbol).
508+
Do(context.Background())
509+
510+
if err != nil {
511+
return fmt.Errorf("获取未完成订单失败: %w", err)
512+
}
513+
514+
// 过滤出止盈单并取消
515+
canceledCount := 0
516+
for _, order := range orders {
517+
orderType := order.Type
518+
519+
// 只取消止盈订单(不取消止损订单)
520+
if orderType == futures.OrderTypeTakeProfitMarket || orderType == futures.OrderTypeTakeProfit {
521+
_, err := t.client.NewCancelOrderService().
522+
Symbol(symbol).
523+
OrderID(order.OrderID).
524+
Do(context.Background())
525+
526+
if err != nil {
527+
log.Printf(" ⚠ 取消止盈单 %d 失败: %v", order.OrderID, err)
528+
continue
529+
}
530+
531+
canceledCount++
532+
log.Printf(" ✓ 已取消止盈单 (订单ID: %d, 类型: %s)", order.OrderID, orderType)
533+
}
534+
}
535+
536+
if canceledCount == 0 {
537+
log.Printf(" ℹ %s 没有止盈单需要取消", symbol)
538+
} else {
539+
log.Printf(" ✓ 已取消 %s 的 %d 个止盈单", symbol, canceledCount)
540+
}
541+
542+
return nil
543+
}
544+
414545
// CancelAllOrders 取消该币种的所有挂单
415546
func (t *FuturesTrader) CancelAllOrders(symbol string) error {
416547
err := t.client.NewCancelAllOpenOrdersService().

trader/hyperliquid_trader.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,56 @@ func (t *HyperliquidTrader) CloseShort(symbol string, quantity float64) (map[str
549549
return result, nil
550550
}
551551

552+
// CancelStopOrders 取消该币种的止盈/止损单
553+
func (t *HyperliquidTrader) CancelStopOrders(symbol string) error {
554+
coin := convertSymbolToHyperliquid(symbol)
555+
556+
// 获取所有挂单
557+
openOrders, err := t.exchange.Info().OpenOrders(t.ctx, t.walletAddr)
558+
if err != nil {
559+
return fmt.Errorf("获取挂单失败: %w", err)
560+
}
561+
562+
// 注意:Hyperliquid SDK 的 OpenOrder 结构不暴露 trigger 字段
563+
// 因此暂时取消该币种的所有挂单(包括止盈止损单)
564+
// 这是安全的,因为在设置新的止盈止损之前,应该清理所有旧订单
565+
canceledCount := 0
566+
for _, order := range openOrders {
567+
if order.Coin == coin {
568+
_, err := t.exchange.Cancel(t.ctx, coin, order.Oid)
569+
if err != nil {
570+
log.Printf(" ⚠ 取消订单失败 (oid=%d): %v", order.Oid, err)
571+
continue
572+
}
573+
canceledCount++
574+
}
575+
}
576+
577+
if canceledCount == 0 {
578+
log.Printf(" ℹ %s 没有挂单需要取消", symbol)
579+
} else {
580+
log.Printf(" ✓ 已取消 %s 的 %d 个挂单(包括止盈/止损单)", symbol, canceledCount)
581+
}
582+
583+
return nil
584+
}
585+
586+
// CancelStopLossOrders 仅取消止损单(Hyperliquid 暂无法区分止损和止盈,取消所有)
587+
func (t *HyperliquidTrader) CancelStopLossOrders(symbol string) error {
588+
// Hyperliquid SDK 的 OpenOrder 结构不暴露 trigger 字段
589+
// 无法区分止损和止盈单,因此取消该币种的所有挂单
590+
log.Printf(" ⚠️ Hyperliquid 无法区分止损/止盈单,将取消所有挂单")
591+
return t.CancelStopOrders(symbol)
592+
}
593+
594+
// CancelTakeProfitOrders 仅取消止盈单(Hyperliquid 暂无法区分止损和止盈,取消所有)
595+
func (t *HyperliquidTrader) CancelTakeProfitOrders(symbol string) error {
596+
// Hyperliquid SDK 的 OpenOrder 结构不暴露 trigger 字段
597+
// 无法区分止损和止盈单,因此取消该币种的所有挂单
598+
log.Printf(" ⚠️ Hyperliquid 无法区分止损/止盈单,将取消所有挂单")
599+
return t.CancelStopOrders(symbol)
600+
}
601+
552602
// CancelAllOrders 取消该币种的所有挂单
553603
func (t *HyperliquidTrader) CancelAllOrders(symbol string) error {
554604
coin := convertSymbolToHyperliquid(symbol)

trader/interface.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ type Trader interface {
3636
// SetTakeProfit 设置止盈单
3737
SetTakeProfit(symbol string, positionSide string, quantity, takeProfitPrice float64) error
3838

39+
// CancelStopOrders 取消该币种的止盈/止损单(已废弃:会同时删除止损和止盈)
40+
// 请使用 CancelStopLossOrders 或 CancelTakeProfitOrders
41+
CancelStopOrders(symbol string) error
42+
43+
// CancelStopLossOrders 仅取消止损单(修复 BUG:调整止损时不删除止盈)
44+
CancelStopLossOrders(symbol string) error
45+
46+
// CancelTakeProfitOrders 仅取消止盈单(修复 BUG:调整止盈时不删除止损)
47+
CancelTakeProfitOrders(symbol string) error
48+
3949
// CancelAllOrders 取消该币种的所有挂单
4050
CancelAllOrders(symbol string) error
4151

0 commit comments

Comments
 (0)