misc updates

This commit is contained in:
jackyu66git
2026-05-21 14:37:49 +08:00
parent d38782490c
commit b2ff322ef3
3 changed files with 290 additions and 4 deletions
+249
View File
@@ -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)
}
File diff suppressed because one or more lines are too long
+1 -4
View File
@@ -49,10 +49,7 @@ if [ "$CLEAN_DB" = true ]; then
fi
fi
# 设置代理(Clash Verge 本地代理,用于 Binance/OKX/Bitget WS 连接
export HTTPS_PROXY=http://127.0.0.1:7897
export HTTP_PROXY=http://127.0.0.1:7897
export NO_PROXY="localhost,127.0.0.1"
# 代理已禁用Clash Verge 未运行
# 编译
NEED_BUILD=false