misc updates
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.Ltime | log.Lmicroseconds)
|
||||
log.Println("=== Bitget 开仓/平仓测试 v2 ===")
|
||||
|
||||
apiKey := os.Getenv("BITGET_API_KEY")
|
||||
apiSecret := os.Getenv("BITGET_API_SECRET")
|
||||
passphrase := os.Getenv("BITGET_PASSPHRASE")
|
||||
if apiKey == "" || apiSecret == "" || passphrase == "" {
|
||||
log.Fatal("环境变量: BITGET_API_KEY, BITGET_API_SECRET, BITGET_PASSPHRASE")
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
headers := func(method, path, body string) map[string]string {
|
||||
ts := fmt.Sprintf("%d", time.Now().UnixMilli())
|
||||
raw := ts + method + path + body
|
||||
mac := hmac.New(sha256.New, []byte(apiSecret))
|
||||
mac.Write([]byte(raw))
|
||||
sign := base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||
return map[string]string{
|
||||
"ACCESS-KEY": apiKey,
|
||||
"ACCESS-SIGN": sign,
|
||||
"ACCESS-TIMESTAMP": ts,
|
||||
"ACCESS-PASSPHRASE": passphrase,
|
||||
}
|
||||
}
|
||||
|
||||
symbol := "MEMEUSDT"
|
||||
mode := "open"
|
||||
if len(os.Args) > 1 {
|
||||
mode = os.Args[1]
|
||||
}
|
||||
|
||||
if mode == "close" {
|
||||
// === 只平仓 (手动) ===
|
||||
log.Println("--- 只用 holdSide 测试平仓 ---")
|
||||
closeWithHoldSide(client, headers, symbol, "17280.1105", "long")
|
||||
pos := checkPos(client, headers, symbol)
|
||||
if pos != "" {
|
||||
log.Printf("⚠️ 仍有持仓: %s", pos)
|
||||
} else {
|
||||
log.Println("✅ 已清仓")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// === 开仓 + 平仓 (自动) ===
|
||||
price := getPrice(client)
|
||||
log.Printf("当前价格: $%.6f", price)
|
||||
|
||||
sz := flooredSize(price, 10, 4)
|
||||
log.Printf("目标开仓数量: %s (for $10)", sz)
|
||||
|
||||
// 开仓
|
||||
log.Println("\n--- 开仓: BUY (Long, open) ---")
|
||||
openOID := doPlace(client, headers, map[string]interface{}{
|
||||
"marginCoin": "USDT", "symbol": symbol,
|
||||
"productType": "USDT-FUTURES", "side": "buy",
|
||||
"orderType": "market", "timeInForce": "IOC",
|
||||
"marginMode": "crossed", "tradeSide": "open",
|
||||
"size": sz,
|
||||
})
|
||||
log.Printf("开仓 orderID=%s", openOID)
|
||||
|
||||
// 等 + 查持仓
|
||||
time.Sleep(3 * time.Second)
|
||||
pos := checkPos(client, headers, symbol)
|
||||
log.Printf("开仓后持仓: %s", pos)
|
||||
|
||||
if pos != "" {
|
||||
// 有仓 → 尝试平仓 (holdSide)
|
||||
log.Println("\n--- 平仓: SELL close holdSide=long ---")
|
||||
closeWithHoldSide(client, headers, symbol, sz, "long")
|
||||
time.Sleep(1 * time.Second)
|
||||
pos2 := checkPos(client, headers, symbol)
|
||||
if pos2 != "" {
|
||||
// 试另一种方式: 不带 holdSide
|
||||
log.Println("\n--- 再试: SELL close 不带holdSide ---")
|
||||
_, err := doPlaceRaw(client, headers, map[string]interface{}{
|
||||
"marginCoin": "USDT", "symbol": symbol,
|
||||
"productType": "USDT-FUTURES", "side": "sell",
|
||||
"orderType": "market", "timeInForce": "IOC",
|
||||
"marginMode": "crossed", "tradeSide": "close",
|
||||
"size": sz,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("❌ 无 holdSide 也失败: %v", err)
|
||||
} else {
|
||||
log.Println("✅ 无holdSide平仓成功!")
|
||||
}
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
log.Printf("最终持仓: %s", checkPos(client, headers, symbol))
|
||||
} else {
|
||||
log.Println("ℹ️ 无持仓,可能开仓未成交")
|
||||
}
|
||||
|
||||
log.Println("\n=== 测试完成 ===")
|
||||
}
|
||||
|
||||
func closeWithHoldSide(client *http.Client, hdr func(m, p, b string) map[string]string, symbol, size, holdSide string) {
|
||||
oid, err := doPlaceRaw(client, hdr, map[string]interface{}{
|
||||
"marginCoin": "USDT", "symbol": symbol,
|
||||
"productType": "USDT-FUTURES", "side": "sell",
|
||||
"orderType": "market", "timeInForce": "IOC",
|
||||
"marginMode": "crossed", "tradeSide": "close",
|
||||
"holdSide": holdSide, "size": size,
|
||||
})
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "22002") {
|
||||
log.Printf("❌ holdSide=%s 返回 22002(无仓位可平)", holdSide)
|
||||
} else {
|
||||
log.Printf("❌ holdSide=%s 失败: %v", holdSide, err)
|
||||
}
|
||||
} else {
|
||||
log.Printf("✅ holdSide=%s 平仓成功 orderID=%s", holdSide, oid)
|
||||
}
|
||||
}
|
||||
|
||||
// --- helpers 跟之前一样 ---
|
||||
|
||||
func doPlace(client *http.Client, hdr func(m, p, b string) map[string]string, body map[string]interface{}) string {
|
||||
oid, err := doPlaceRaw(client, hdr, body)
|
||||
if err != nil {
|
||||
log.Fatalf("下单失败: %v", err)
|
||||
}
|
||||
return oid
|
||||
}
|
||||
|
||||
func doPlaceRaw(client *http.Client, hdr func(m, p, b string) map[string]string, body map[string]interface{}) (string, error) {
|
||||
method := "POST"
|
||||
path := "/api/v2/mix/order/place-order"
|
||||
bodyJSON, _ := json.Marshal(body)
|
||||
h := hdr(method, path, string(bodyJSON))
|
||||
|
||||
req, _ := http.NewRequest(method, "https://api.bitget.com"+path, strings.NewReader(string(bodyJSON)))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
for k, v := range h {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("http: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
|
||||
var result struct {
|
||||
Code string `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
OrderID string `json:"orderId"`
|
||||
} `json:"data"`
|
||||
}
|
||||
json.Unmarshal(respBody, &result)
|
||||
log.Printf(" → 返回: code=%s msg=%s orderID=%s", result.Code, result.Msg, result.Data.OrderID)
|
||||
if result.Code != "00000" {
|
||||
return "", fmt.Errorf("%s - %s", result.Code, result.Msg)
|
||||
}
|
||||
return result.Data.OrderID, nil
|
||||
}
|
||||
|
||||
func checkPos(client *http.Client, hdr func(m, p, b string) map[string]string, symbol string) string {
|
||||
method := "GET"
|
||||
path := "/api/v2/mix/position/single-position?symbol=" + symbol + "&productType=USDT-FUTURES&marginCoin=USDT"
|
||||
h := hdr(method, path, "")
|
||||
req, _ := http.NewRequest(method, "https://api.bitget.com"+path, nil)
|
||||
for k, v := range h {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
log.Printf(" → 持仓API: %s", string(respBody))
|
||||
|
||||
var raw struct {
|
||||
Code string `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data []struct {
|
||||
Symbol string `json:"symbol"`
|
||||
HoldSide string `json:"holdSide"`
|
||||
Total string `json:"total"`
|
||||
Available string `json:"available"`
|
||||
} `json:"data"`
|
||||
}
|
||||
json.Unmarshal(respBody, &raw)
|
||||
if raw.Code != "00000" {
|
||||
return ""
|
||||
}
|
||||
if len(raw.Data) > 0 {
|
||||
d := raw.Data[0]
|
||||
return fmt.Sprintf("%s %s total=%s avai=%s", d.Symbol, d.HoldSide, d.Total, d.Available)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getPrice(client *http.Client) float64 {
|
||||
resp, err := client.Get("https://api.bitget.com/api/v2/mix/market/tickers?productType=USDT-FUTURES")
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
var raw struct {
|
||||
Code string `json:"code"`
|
||||
Data []struct {
|
||||
Symbol string `json:"symbol"`
|
||||
Last string `json:"lastPr"`
|
||||
} `json:"data"`
|
||||
}
|
||||
json.Unmarshal(body, &raw)
|
||||
for _, d := range raw.Data {
|
||||
if d.Symbol == "MEMEUSDT" {
|
||||
p, _ := strconv.ParseFloat(d.Last, 64)
|
||||
return p
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func flooredSize(price float64, usd, decimals int) string {
|
||||
sz := float64(usd) / price
|
||||
div := 1
|
||||
for i := 0; i < decimals; i++ {
|
||||
div *= 10
|
||||
}
|
||||
f := float64(div)
|
||||
floored := float64(int64(sz*f)) / f
|
||||
return fmt.Sprintf("%."+fmt.Sprintf("%d", decimals)+"f", floored)
|
||||
}
|
||||
Reference in New Issue
Block a user