This commit is contained in:
2025-02-06 11:14:33 +08:00
commit 07847a2d9e
535 changed files with 65131 additions and 0 deletions

View File

@ -0,0 +1,808 @@
package binanceservice
import (
"context"
"errors"
"fmt"
DbModels "go-admin/app/admin/models"
"go-admin/app/admin/service/dto"
"go-admin/common/const/rediskey"
commondto "go-admin/common/dto"
"go-admin/common/global"
"go-admin/common/helper"
"go-admin/models"
"go-admin/models/spot"
"go-admin/pkg/httputils"
"go-admin/pkg/utility"
"go-admin/pkg/utility/snowflakehelper"
"strconv"
"strings"
"time"
"github.com/go-redis/redis/v8"
"github.com/jinzhu/copier"
"github.com/shopspring/decimal"
"gorm.io/gorm"
"github.com/bytedance/sonic"
log "github.com/go-admin-team/go-admin-core/logger"
)
const (
binanceRestApi = "https://api.binance.com"
)
var ErrorMaps = map[float64]string{
-2021: "订单已拒绝。请调整触发价并重新下订单。 对于买入/做多,止盈订单触发价应低于市场价,止损订单的触发价应高于市场价。卖出/做空则与之相反",
-4164: "下单失败。少于最小下单金额",
-4061: "持仓方向需要设置为单向持仓。",
-2019: "保证金不足",
-1111: "金额设置错误。精度错误",
-1021: "请求的时间戳在recvWindow之外",
-2011: "该交易对没有订单",
-2010: "账户余额不足",
}
type SpotRestApi struct {
}
/*
获取 现货交易-规范信息
- return @info 规范信息
- return @err 错误信息
*/
func (e SpotRestApi) GetExchangeInfo() (symbols []spot.Symbol, err error) {
url := fmt.Sprintf("%s%s?permissions=SPOT", binanceRestApi, "/api/v3/exchangeInfo")
mapData, err := httputils.NewHttpRequestWithFasthttp("GET", url, "", map[string]string{})
if err != nil {
return
}
if len(mapData) == 0 {
err = errors.New("获取现货交易-规范信息数量为空")
return
}
var info spot.ExchangeInfo
err = sonic.Unmarshal(mapData, &info)
if err == nil {
return info.Symbols, nil
}
return
}
/*
获取现货24h行情变更
*/
func (e SpotRestApi) GetSpotTicker24h(tradeSet *map[string]models.TradeSet) (deleteSymbols []string, err error) {
tickerApi := fmt.Sprintf("%s%s", binanceRestApi, "/api/v3/ticker/24hr")
mapData, err := httputils.NewHttpRequestWithFasthttp("GET", tickerApi, "", map[string]string{})
if err != nil {
return []string{}, err
}
deleteSymbols = make([]string, 0)
if len(mapData) == 0 {
return deleteSymbols, errors.New("获取交易对失败,或数量为空")
}
tickers := make([]spot.SpotTicker24h, 0)
err = sonic.Unmarshal([]byte(mapData), &tickers)
if err != nil {
log.Error("反序列化json失败", err)
}
for _, item := range tickers {
key := fmt.Sprintf("%s:%s", global.TICKER_SPOT, item.Symbol)
symbol, exits := (*tradeSet)[item.Symbol]
if !exits {
helper.DefaultRedis.DeleteString(key)
continue
}
symbol.OpenPrice = utility.StringAsFloat(item.OpenPrice)
symbol.PriceChange = utility.StringAsFloat(item.PriceChangePercent)
symbol.LowPrice = item.LowPrice
symbol.HighPrice = item.HighPrice
symbol.Volume = item.Volume
symbol.QuoteVolume = item.QuoteVolume
symbol.LastPrice = item.LastPrice
val, err := sonic.Marshal(symbol)
if !strings.HasSuffix(item.Symbol, symbol.Currency) || item.Count <= 0 || utility.StringToFloat64(item.QuoteVolume) <= 0 {
helper.DefaultRedis.DeleteString(key)
deleteSymbols = append(deleteSymbols, item.Symbol)
continue
}
if err != nil {
log.Error("序列化失败", item.Symbol)
continue
}
err = helper.DefaultRedis.SetString(key, string(val))
if err != nil {
log.Error("缓存交易对失败|", item.Symbol, err)
}
helper.DefaultRedis.AddSortSet(global.COIN_PRICE_CHANGE, symbol.PriceChange, symbol.Coin)
}
return deleteSymbols, nil
}
/*
获取单个交易对24h行情
- @symbol 交易对
- @data 结果
*/
func (e SpotRestApi) GetSpotTicker24(symbol string, data *models.Ticker24, tradeSet *models.TradeSet) error {
key := fmt.Sprintf("%s:%s", global.TICKER_SPOT, symbol)
val, err := helper.DefaultRedis.GetString(key)
if err != nil {
return err
}
err = sonic.Unmarshal([]byte(val), tradeSet)
if err != nil {
return err
}
if tradeSet.Coin != "" {
data.HighPrice = tradeSet.HighPrice
data.ChangePercent = fmt.Sprintf("%g", tradeSet.PriceChange)
data.LastPrice = tradeSet.LastPrice
data.LowPrice = tradeSet.LowPrice
data.OpenPrice = fmt.Sprintf("%g", tradeSet.OpenPrice)
data.QuoteVolume = tradeSet.QuoteVolume
data.Volume = tradeSet.Volume
}
return nil
}
type Ticker struct {
Symbol string `json:"symbol"`
Price string `json:"price"`
}
func (e SpotRestApi) Ticker() {
tickerApi := fmt.Sprintf("%s%s", binanceRestApi, "/api/v3/ticker/price")
mapData, _ := httputils.NewHttpRequestWithFasthttp("GET", tickerApi, "", map[string]string{})
//sonic.Unmarshal(mapData, &tickerData)
helper.DefaultRedis.SetString(rediskey.SpotSymbolTicker, string(mapData))
}
// OrderPlace 现货下单
func (e SpotRestApi) OrderPlace(orm *gorm.DB, params OrderPlacementService) error {
if orm == nil {
return errors.New("数据库实例为空")
}
err2 := params.CheckParams()
if err2 != nil {
return err2
}
paramsMaps := map[string]string{
"symbol": params.Symbol,
"side": params.Side,
"quantity": params.Quantity.String(),
"type": params.Type,
"newClientOrderId": params.NewClientOrderId,
}
if strings.ToUpper(params.Type) != "MARKET" { //市价
paramsMaps["price"] = params.Price.String()
paramsMaps["timeInForce"] = "GTC"
if strings.ToUpper(params.Type) == "TAKE_PROFIT_LIMIT" || strings.ToUpper(params.Type) == "STOP_LOSS_LIMIT" {
paramsMaps["stopPrice"] = params.StopPrice.String()
}
}
var apiUserInfo DbModels.LineApiUser
err := orm.Model(&DbModels.LineApiUser{}).Where("id = ?", params.ApiId).Find(&apiUserInfo).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
log.Errorf("api用户出错 err: %+v", err)
return err
}
client := GetClient(&apiUserInfo)
resp, _, err := client.SendSpotAuth("/api/v3/order", "POST", paramsMaps)
if err != nil {
dataMap := make(map[string]interface{})
if err.Error() != "" {
if err := sonic.Unmarshal([]byte(err.Error()), &dataMap); err != nil {
return fmt.Errorf("api_id:%d 交易对:%s 下订单失败:%+v", apiUserInfo.Id, params.Symbol, err.Error())
}
}
code, ok := dataMap["code"]
if ok {
errContent := ErrorMaps[code.(float64)]
paramsVal, _ := sonic.MarshalString(paramsMaps)
log.Error("api_id:", utility.IntToString(apiUserInfo.Id), " 交易对:", params.Symbol, " 下单参数:", paramsVal)
if errContent == "" {
errContent, _ = dataMap["msg"].(string)
}
return fmt.Errorf("api_id:%d 交易对:%s 下订单失败:%s", apiUserInfo.Id, params.Symbol, errContent)
}
if strings.Contains(err.Error(), "Unknown order sent.") {
return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%+v", apiUserInfo.Id, params.Symbol, ErrorMaps[-2011])
}
return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%v", apiUserInfo.Id, params.Symbol, err)
}
var dataMap map[string]interface{}
if err := sonic.Unmarshal(resp, &dataMap); err != nil {
return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%+v", apiUserInfo.Id, params.Symbol, err.Error())
}
//code, ok := dataMap["code"]
//if !ok {
// return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%s", apiUserInfo.Id, params.Symbol, dataMap["message"])
//
//}
//if code.(float64) != 200 {
// return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%s", apiUserInfo.Id, params.Symbol, dataMap["message"])
//}
return nil
}
// CancelOpenOrders 撤销单一交易对下所有挂单 包括了来自订单列表的挂单
func (e SpotRestApi) CancelOpenOrders(orm *gorm.DB, req CancelOpenOrdersReq) error {
if orm == nil {
return errors.New("数据库实例为空")
}
err := req.CheckParams()
if err != nil {
return err
}
params := map[string]string{
"symbol": req.Symbol,
}
var apiUserInfo DbModels.LineApiUser
err = orm.Model(&DbModels.LineApiUser{}).Where("id = ?", req.ApiId).Find(&apiUserInfo).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("api_id:%d 交易对:%s api用户出错:%+v", apiUserInfo.Id, req.Symbol, err)
}
var client *helper.BinanceClient
if apiUserInfo.UserPass == "" {
client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress)
} else {
client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress)
}
_, _, err = client.SendSpotAuth("/api/v3/openOrders", "DELETE", params)
if err != nil {
dataMap := make(map[string]interface{})
if err.Error() != "" {
if err := sonic.Unmarshal([]byte(err.Error()), &dataMap); err != nil {
return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%+v", apiUserInfo.Id, req.Symbol, err.Error())
}
}
code, ok := dataMap["code"]
if ok {
return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%s", apiUserInfo.Id, req.Symbol, ErrorMaps[code.(float64)])
}
if strings.Contains(err.Error(), "Unknown order sent.") {
return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%+v", apiUserInfo.Id, req.Symbol, ErrorMaps[-2011])
}
return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%+v", apiUserInfo.Id, req.Symbol, err.Error())
}
return nil
}
// CancelOpenOrderByOrderSn 通过单一订单号取消委托
func (e SpotRestApi) CancelOpenOrderByOrderSn(apiUserInfo DbModels.LineApiUser, symbol string, newClientOrderId string) error {
params := map[string]string{
"symbol": symbol,
"origClientOrderId": newClientOrderId,
"recvWindow": "10000",
}
var client *helper.BinanceClient
if apiUserInfo.UserPass == "" {
client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress)
} else {
client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress)
}
_, code, err := client.SendSpotAuth("/api/v3/order ", "DELETE", params)
if err != nil || code != 200 {
log.Error("取消现货委托失败 参数:", params)
log.Error("取消现货委托失败 code:", code)
log.Error("取消现货委托失败 err:", err)
dataMap := make(map[string]interface{})
if err.Error() != "" {
if err := sonic.Unmarshal([]byte(err.Error()), &dataMap); err != nil {
return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%+v", apiUserInfo.Id, symbol, err.Error())
}
}
code, ok := dataMap["code"]
if ok {
return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%s", apiUserInfo.Id, symbol, ErrorMaps[code.(float64)])
}
if strings.Contains(err.Error(), "Unknown order sent.") {
return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%+v", apiUserInfo.Id, symbol, ErrorMaps[-2011])
}
return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%+v", apiUserInfo.Id, symbol, err.Error())
}
return nil
}
// CalcEntryCashPriceByOrder 计算现货主单均价
func CalcEntryCashPriceByOrder(orderInfo *DbModels.LinePreOrder, orm *gorm.DB) (EntryPriceResult, error) {
//找到主单成交的记录
var id int
if orderInfo.Pid > 0 {
id = orderInfo.Pid
} else {
id = orderInfo.Id
}
orderLists := make([]DbModels.LinePreOrder, 0)
orm.Model(&DbModels.LinePreOrder{}).Where(" symbol = ? AND site = 'BUY' AND order_type in ('1','8') AND status ='9' AND (id = ? OR pid = ?)", orderInfo.Symbol, id, id).Find(&orderLists)
var (
totalNum decimal.Decimal //总成交数量
totalMoney decimal.Decimal //总金额
entryPrice decimal.Decimal //均价
initPrice decimal.Decimal //主单下单价格
firstId int //主单id
)
for _, list := range orderLists {
num, _ := decimal.NewFromString(list.Num)
totalNum = totalNum.Add(num)
price, _ := decimal.NewFromString(list.Price)
totalMoney = totalMoney.Add(num.Mul(price))
if list.OrderType == "1" {
firstId = list.Id
initPrice, _ = decimal.NewFromString(list.Price)
}
}
if totalNum.GreaterThan(decimal.Zero) {
entryPrice = totalMoney.Div(totalNum)
}
return EntryPriceResult{
TotalNum: totalNum,
EntryPrice: entryPrice,
FirstPrice: initPrice,
FirstId: firstId,
TotalMoney: totalMoney,
}, nil
}
// ClosePosition 平仓
// symbol 交易对
// orderSn 平仓单号
// quantity 平仓数量
// side 原始仓位方向
// apiUserInfo 用户信息
// orderType 平仓类型 限价LIMIT 市价()
func (e SpotRestApi) ClosePosition(symbol string, orderSn string, quantity decimal.Decimal, side string,
apiUserInfo DbModels.LineApiUser, orderType string, rate string, price decimal.Decimal) error {
endpoint := "/api/v3/order "
params := map[string]string{
"symbol": symbol,
"type": orderType,
"quantity": quantity.String(),
"newClientOrderId": orderSn,
}
if side == "SELL" {
params["side"] = "BUY"
} else {
params["side"] = "SELL"
}
if orderType == "LIMIT" {
key := fmt.Sprintf("%s:%s", global.TICKER_SPOT, symbol)
tradeSet, _ := helper.GetObjString[models.TradeSet](helper.DefaultRedis, key)
rateFloat, _ := decimal.NewFromString(rate)
if rateFloat.GreaterThan(decimal.Zero) {
if side == "SELL" { //仓位是空 平空的话
price = price.Mul(decimal.NewFromInt(1).Add(rateFloat)).Truncate(int32(tradeSet.PriceDigit))
} else {
price = price.Mul(decimal.NewFromInt(1).Sub(rateFloat)).Truncate(int32(tradeSet.PriceDigit))
}
params["price"] = price.String()
}
}
params["timeInForce"] = "GTC"
client := GetClient(&apiUserInfo)
resp, _, err := client.SendFuturesRequestAuth(endpoint, "POST", params)
if err != nil {
var dataMap map[string]interface{}
if err2 := sonic.Unmarshal([]byte(err.Error()), &dataMap); err2 != nil {
return fmt.Errorf("api_id:%d 交易对:%s 平仓出错:%s", apiUserInfo.Id, symbol, err.Error())
}
code, ok := dataMap["code"]
if ok {
errContent := FutErrorMaps[code.(float64)]
if errContent == "" {
errContent = err.Error()
}
return fmt.Errorf("api_id:%d 交易对:%s 平仓出错:%s", apiUserInfo.Id, symbol, errContent)
}
}
var orderResp FutOrderResp
err = sonic.Unmarshal(resp, &orderResp)
if err != nil {
return fmt.Errorf("api_id:%d 交易对:%s 平仓出错:%s", apiUserInfo.Id, symbol, err.Error())
}
if orderResp.Symbol == "" {
return fmt.Errorf("api_id:%d 交易对:%s 平仓出错:未找到订单信息", apiUserInfo.Id, symbol)
}
return nil
}
func GetClient(apiUserInfo *DbModels.LineApiUser) *helper.BinanceClient {
var client *helper.BinanceClient
if apiUserInfo.UserPass == "" {
client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress)
} else {
client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress)
}
return client
}
/*
重下止盈单
*/
func (e SpotRestApi) reTakeOrder(parentOrderInfo DbModels.LinePreOrder, orm *gorm.DB) {
price, _ := decimal.NewFromString(parentOrderInfo.Price)
num, _ := decimal.NewFromString(parentOrderInfo.Num)
parentId := parentOrderInfo.Id
if parentOrderInfo.Pid > 0 {
parentId = parentOrderInfo.Pid
}
var takePrice decimal.Decimal
holdeAKey := fmt.Sprintf(rediskey.HoldeA, parentId)
holdeAVal, _ := helper.DefaultRedis.GetString(holdeAKey)
holdeA := HoldeData{}
if holdeAVal != "" {
sonic.Unmarshal([]byte(holdeAVal), &holdeA)
}
//查询持仓失败
if holdeA.Id == 0 {
log.Error("查询A账号持仓失败")
return
}
//加仓次数大于0 就需要使用均价
if holdeA.PositionIncrementCount > 0 {
price = holdeA.AveragePrice
num = holdeA.TotalQuantity
}
if parentOrderInfo.Site == "BUY" {
takePrice = price.Mul(decimal.NewFromInt(100).Add(parentOrderInfo.ProfitRate)).Div(decimal.NewFromInt(100))
} else {
takePrice = price.Mul(decimal.NewFromInt(100).Sub(parentOrderInfo.ProfitRate)).Div(decimal.NewFromInt(100))
}
var takeOrder, oldTakeOrder DbModels.LinePreOrder
if err := orm.Model(&oldTakeOrder).Where("pid= ? AND order_type ='5'", parentId).First(&oldTakeOrder).Error; err != nil {
log.Error("查询止盈单失败")
return
}
tradeset, _ := GetTradeSet(oldTakeOrder.Symbol, 0)
if tradeset.Coin == "" {
log.Error("查询交易对失败")
return
}
copier.Copy(&takeOrder, &oldTakeOrder)
takeOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10)
takeOrder.Price = takePrice.Truncate(int32(tradeset.PriceDigit)).String()
takeOrder.Num = num.Mul(decimal.NewFromFloat(0.995)).Truncate(int32(tradeset.AmountDigit)).String()
takeOrder.Desc = ""
takeOrder.Status = 0
takeOrder.Id = 0
if err := orm.Create(&takeOrder).Error; err != nil {
log.Error("创建新止盈单失败")
return
}
params := OrderPlacementService{
ApiId: parentOrderInfo.ApiId,
Symbol: parentOrderInfo.Symbol,
Price: utility.StrToDecimal(takeOrder.Price),
Quantity: utility.StringToDecimal(takeOrder.Num),
Side: "SELL",
Type: "TAKE_PROFIT_LIMIT",
TimeInForce: "GTC",
StopPrice: utility.StrToDecimal(takeOrder.Price),
NewClientOrderId: takeOrder.OrderSn,
}
apiUserInfo, _ := GetApiInfo(parentOrderInfo.ApiId)
if apiUserInfo.Id == 0 {
log.Error("获取用户api失败")
return
}
if err := CancelSpotOrder(parentOrderInfo.Symbol, &apiUserInfo, "SELL"); err != nil {
log.Error("取消旧止盈失败 err:", err)
} else {
if err := orm.Model(&DbModels.LinePreOrder{}).Where("pid = ? AND order_type =5 AND status in ('0','1','5') AND order_sn !=?", parentId, takeOrder.OrderSn).Update("status", "4").Error; err != nil {
log.Error("更新旧止盈单取消状态失败 err:", err)
}
}
var err error
for x := 1; x <= 4; x++ {
err = e.OrderPlace(orm, params)
if err == nil {
break
}
log.Error("下止盈单失败 第", utility.IntToString(x), "次", " err:", err)
time.Sleep(2 * time.Second * time.Duration(x))
}
if err != nil {
log.Error("重新下单止盈失败 err:", err)
if err1 := orm.Model(&DbModels.LinePreOrder{}).Where("order_sn =?", takeOrder.OrderSn).
Updates(map[string]interface{}{"status": "2", "desc": err.Error()}).Error; err1 != nil {
log.Error("重新下单止盈 修改订单失败 pid:", parentId, " takePrice:", takePrice, " err:", err1)
}
}
//修改止盈单信息
if err := orm.Model(&DbModels.LinePreOrder{}).Where("pid =? AND order_type ='5'", parentId).
Updates(map[string]interface{}{"price": takePrice, "rate": parentOrderInfo.ProfitRate}).Error; err != nil {
log.Error("重新下单止盈 修改订单失败 pid:", parentId, " takePrice:", takePrice, " rate:", parentOrderInfo.ProfitRate, " err:", err)
}
}
/*
判断是否触发
*/
func JudgeSpotPrice(trade models.TradeSet) {
preOrderVal, _ := helper.DefaultRedis.GetAllList(rediskey.PreSpotOrderList)
db := GetDBConnection()
if len(preOrderVal) == 0 {
// log.Debug("没有现货预下单")
return
}
spotApi := SpotRestApi{}
for _, item := range preOrderVal {
preOrder := dto.PreOrderRedisList{}
if err := sonic.Unmarshal([]byte(item), &preOrder); err != nil {
log.Error("反序列化失败")
continue
}
if preOrder.Symbol == trade.Coin+trade.Currency {
orderPrice, _ := decimal.NewFromString(preOrder.Price)
tradePrice, _ := decimal.NewFromString(trade.LastPrice)
//买入
if strings.ToUpper(preOrder.Site) == "BUY" && orderPrice.Cmp(tradePrice) >= 0 && orderPrice.Cmp(decimal.Zero) > 0 && tradePrice.Cmp(decimal.Zero) > 0 {
SpotOrderLock(db, &preOrder, item, spotApi)
}
}
}
}
// 分布式锁下单
// v 预下单信息
// item 预下单源文本
func SpotOrderLock(db *gorm.DB, v *dto.PreOrderRedisList, item string, spotApi SpotRestApi) {
lock := helper.NewRedisLock(fmt.Sprintf(rediskey.SpotTrigger, v.ApiId, v.Symbol), 20, 5, 100*time.Millisecond)
if ok, err := lock.AcquireWait(context.Background()); err != nil {
log.Error("获取锁失败", err)
return
} else if ok {
defer lock.Release()
key := fmt.Sprintf(rediskey.UserHolding, v.ApiId)
symbols, err := helper.DefaultRedis.GetAllList(key)
if err != nil && err != redis.Nil {
log.Error("获取用户持仓失败", err)
return
}
preOrder := DbModels.LinePreOrder{}
if err := db.Where("id = ?", v.Id).First(&preOrder).Error; err != nil {
log.Error("获取预下单失败", err)
if errors.Is(err, gorm.ErrRecordNotFound) {
log.Error("不存在待触发主单", item)
helper.DefaultRedis.LRem(rediskey.PreSpotOrderList, item)
}
return
}
//获取代币 是否已有持仓
coin := utility.ReplaceSuffix(preOrder.Symbol, preOrder.QuoteSymbol, "")
if utility.ContainsStr(symbols, coin) {
log.Debug("已有持仓")
return
}
hasrecord, _ := helper.DefaultRedis.IsElementInList(rediskey.PreSpotOrderList, item)
if !hasrecord {
log.Error("不存在待触发主单", item)
return
}
price, _ := decimal.NewFromString(v.Price)
num, _ := decimal.NewFromString(preOrder.Num)
params := OrderPlacementService{
ApiId: v.ApiId,
Symbol: v.Symbol,
Side: v.Site,
Type: preOrder.MainOrderType,
TimeInForce: "GTC",
Price: price,
Quantity: num,
NewClientOrderId: v.OrderSn,
}
preOrderVal, _ := sonic.MarshalString(&v)
if err := spotApi.OrderPlace(db, params); err != nil {
log.Error("下单失败", v.Symbol, " err:", err)
err := db.Model(&DbModels.LinePreOrder{}).Where("id =? AND status ='0'", preOrder.Id).Updates(map[string]interface{}{"status": "2", "desc": err.Error()}).Error
if err != nil {
log.Error("下单失败后修改订单失败")
}
if preOrderVal != "" {
if _, err := helper.DefaultRedis.LRem(rediskey.PreSpotOrderList, preOrderVal); err != nil {
log.Error("删除redis 预下单失败:", err)
}
}
return
}
if preOrderVal != "" {
if _, err := helper.DefaultRedis.LRem(rediskey.PreSpotOrderList, preOrderVal); err != nil {
log.Error("删除redis 预下单失败:", err)
}
// spotPreOrders, _ := helper.DefaultRedis.GetAllList(rediskey.PreSpotOrderList)
// futuresPreOrders, _ := helper.DefaultRedis.GetAllList(rediskey.PreFutOrderList)
// var order dto.PreOrderRedisList
// for _, item := range spotPreOrders {
// sonic.Unmarshal([]byte(item), &order)
// if order.QuoteSymbol == "" {
// }
// }
}
if err := db.Model(&DbModels.LinePreOrder{}).Where("id =? AND status ='0'", preOrder.Id).Update("status", "1").Error; err != nil {
log.Error("更新预下单状态失败 ordersn:", v.OrderSn, " status:1")
}
if err := helper.DefaultRedis.RPushList(key, coin); err != nil {
log.Error("写入用户持仓失败", v.Symbol)
}
return
} else {
log.Error("获取锁失败")
return
}
}
/*
获取api用户信息
*/
func GetApiInfo(apiId int) (DbModels.LineApiUser, error) {
api := DbModels.LineApiUser{}
key := fmt.Sprintf(rediskey.API_USER, apiId)
val, _ := helper.DefaultRedis.GetString(key)
if val != "" {
if err := sonic.UnmarshalString(val, &api); err == nil {
return api, nil
}
}
db := GetDBConnection()
if err := db.Model(&api).Where("id =?", apiId).First(&api).Error; err != nil {
return api, err
}
val, _ = sonic.MarshalString(&api)
if val != "" {
helper.DefaultRedis.SetString(key, val)
}
return api, nil
}
/*
根据A账户获取B账号信息
*/
func GetChildApiInfo(apiId int) (DbModels.LineApiUser, error) {
var api DbModels.LineApiUser
childApiId := 0
groups := GetApiGroups()
for _, item := range groups {
if item.ApiUserId == apiId {
childApiId = item.ChildApiUserId
break
}
}
if childApiId > 0 {
return GetApiInfo(childApiId)
}
return api, nil
}
func GetApiGroups() []commondto.ApiGroupDto {
apiGroups := make([]commondto.ApiGroupDto, 0)
apiGroupStr, _ := helper.DefaultRedis.GetAllKeysAndValues(rediskey.ApiGroupAll)
if len(apiGroupStr) == 0 {
return apiGroups
}
for _, item := range apiGroupStr {
apiGroup := commondto.ApiGroupDto{}
if err := sonic.UnmarshalString(item, &apiGroup); err != nil {
log.Error("groups 序列化失败", err)
continue
}
apiGroups = append(apiGroups, apiGroup)
}
return apiGroups
}
// GetSpotSymbolLastPrice 获取现货交易对最新价格
func (e SpotRestApi) GetSpotSymbolLastPrice(targetSymbol string) (lastPrice decimal.Decimal) {
tickerSymbol := helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val()
tickerSymbolMaps := make([]dto.Ticker, 0)
sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps)
//key := fmt.Sprintf("%s:%s", global.TICKER_SPOT, targetSymbol)
//tradeSet, _ := helper.GetObjString[models.TradeSet](helper.DefaultRedis, key)
for _, symbolMap := range tickerSymbolMaps {
if symbolMap.Symbol == strings.ToUpper(targetSymbol) {
lastPrice = utility.StringToDecimal(symbolMap.Price)
}
}
return lastPrice
}

View File

@ -0,0 +1,205 @@
package binanceservice
import (
"fmt"
"github.com/bytedance/sonic"
"go-admin/app/admin/models"
"go-admin/common/helper"
"go-admin/pkg/utility"
"go-admin/pkg/utility/snowflakehelper"
"testing"
"github.com/shopspring/decimal"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func TestCancelFutClosePosition(t *testing.T) {
dsn := "root:root@tcp(192.168.123.216:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
var apiUserInfo models.LineApiUser
db.Model(&models.LineApiUser{}).Where("id = 21").Find(&apiUserInfo)
err := CancelFutClosePosition(apiUserInfo, "ADAUSDT", "BUY", "SHORT")
if err != nil {
t.Log("err:", err)
}
fmt.Println("成功")
}
func TestDecimal(t *testing.T) {
fromString, err := decimal.NewFromString("")
if err != nil {
fmt.Println("err:", err)
}
fmt.Println(fromString)
}
func TestPositionV3(t *testing.T) {
api := FutRestApi{}
dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
var apiUserInfo models.LineApiUser
db.Model(&models.LineApiUser{}).Where("id = 21").Find(&apiUserInfo)
err := CancelFutClosePosition(apiUserInfo, "ADAUSDT", "BUY", "SHORT")
if err != nil {
t.Log("err:", err)
}
v3, err := api.GetPositionV3(&apiUserInfo, "DOGEUSDT")
if err != nil {
t.Log("err:", err)
}
fmt.Println(v3)
}
func TestInsertLog(t *testing.T) {
dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
//初始redis 链接
helper.InitDefaultRedis("192.168.1.12:6379", "", 0)
helper.InitLockRedisConn("192.168.1.12:6379", "", "0")
InsertProfitLogs(db, "367452130811838464", decimal.NewFromInt(20), decimal.NewFromFloat(0.34078000))
}
func TestCancelSpotOrder(t *testing.T) {
//dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
//db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
//var apiUserInfo models.LineApiUser
//db.Model(&models.LineApiUser{}).Where("id = 21").Find(&apiUserInfo)
//api := SpotRestApi{}
//dto.CancelOpenOrderReq{
// ApiId: 21,
// Symbol: "ADAUSDT",
// OrderSn: utility.Int64ToString(snowflakehelper.GetOrderId()),
// OrderType: 0,
//}
//api.CancelOpenOrders()
}
func TestCancelAllFutOrder(t *testing.T) {
dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
var apiUserInfo models.LineApiUser
db.Model(&models.LineApiUser{}).Where("id = 21").Find(&apiUserInfo)
api := FutRestApi{}
api.CancelAllFutOrder(apiUserInfo, "TRUMPUSDT")
}
//func TestName(t *testing.T) {
// api := FutRestApi{}
// dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
// db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
// var apiUserInfo models.LineApiUser
// db.Model(&models.LineApiUser{}).Where("id = 21").Find(&apiUserInfo)
// api.CancelBatchFutOrder(apiUserInfo, "ADAUSDT",[]{""})
//}
func TestFutOrderPalce(t *testing.T) {
api := FutRestApi{}
dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
api.OrderPlace(db, FutOrderPlace{
ApiId: 21,
Symbol: "ADAUSDT",
Side: "SELL",
Quantity: decimal.NewFromFloat(7),
Price: decimal.NewFromFloat(0.9764),
SideType: "LIMIT",
OpenOrder: 0,
Profit: decimal.Zero,
StopPrice: decimal.Zero,
OrderType: "LIMIT",
NewClientOrderId: "367580922570080256",
})
}
func TestCancelOpenOrderByOrderSn(t *testing.T) {
api := SpotRestApi{}
dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
var apiUserInfo models.LineApiUser
db.Model(&models.LineApiUser{}).Where("id = ?", 10).Find(&apiUserInfo)
err := api.CancelOpenOrderByOrderSn(apiUserInfo, "DOGEUSDT", "367836524202426368")
if err != nil {
t.Log("err:", err)
} else {
fmt.Println("成功")
}
}
func TestCancelOpenOrderBySymbol(t *testing.T) {
// api := SpotRestApi{}
dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
var apiUserInfo models.LineApiUser
db.Model(&models.LineApiUser{}).Where("id = ?", 10).Find(&apiUserInfo)
err := CancelSpotOrder("ADAUSDT", &apiUserInfo, "SELL")
if err != nil {
t.Log("err:", err)
} else {
fmt.Println("成功")
}
}
func TestDelRedisKeys(t *testing.T) {
//初始redis 链接
helper.InitDefaultRedis("192.168.1.12:6379", "", 0)
helper.InitLockRedisConn("192.168.1.12:6379", "", "0")
prefixs := []string{
"api_user_hold",
"spot_trigger_lock",
"fut_trigger_lock",
"fut_trigger_stop_lock",
"spot_trigger_stop_lock",
"spot_addposition_trigger",
"fut_addposition_trigger",
"spot_hedge_close_position",
"futures_hedge_close_position",
"spot_callback",
"fut_callback",
"holde_a",
"holde_b",
}
helper.DefaultRedis.DeleteKeysByPrefix(prefixs...)
}
func TestOpenOrders(t *testing.T) {
dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
var apiUserInfo models.LineApiUser
db.Model(&models.LineApiUser{}).Where("id = ?", 21).Find(&apiUserInfo)
client := GetClient(&apiUserInfo)
auth, _, err := client.SendSpotAuth("/api/v3/order", "GET", map[string]string{"symbol": "ADAUSDT", "orderId": "6001232151"})
if err != nil {
fmt.Println("err:", err)
}
m := make(map[string]interface{}, 0)
sonic.Unmarshal(auth, &m)
fmt.Println("m:", m)
}
func TestClosePosition(t *testing.T) {
endpoint := "/fapi/v1/order"
params := map[string]string{
"symbol": "ADAUSDT",
"type": "LIMIT",
"quantity": "5",
"newClientOrderId": utility.Int64ToString(snowflakehelper.GetOrderId()),
"positionSide": "SHORT",
}
params["side"] = "BUY"
params["price"] = "0.98"
params["timeInForce"] = "GTC"
dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
var apiUserInfo models.LineApiUser
db.Model(&models.LineApiUser{}).Where("id = ?", 21).Find(&apiUserInfo)
client := GetClient(&apiUserInfo)
resp, _, err := client.SendFuturesRequestAuth(endpoint, "POST", params)
fmt.Println("resp:", string(resp))
fmt.Println("err:", err)
}

View File

@ -0,0 +1,331 @@
package binanceservice
import (
"errors"
"fmt"
DbModels "go-admin/app/admin/models"
"go-admin/app/admin/service/dto"
"go-admin/common/const/rediskey"
"go-admin/common/global"
"go-admin/common/helper"
"go-admin/models"
"go-admin/pkg/utility"
"strings"
"time"
"github.com/bytedance/sonic"
"github.com/go-admin-team/go-admin-core/logger"
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
type AddPosition struct {
Db *gorm.DB
}
// 获取缓存交易对
// symbolType 0-现货 1-合约
func GetTradeSet(symbol string, symbolType int) (models.TradeSet, error) {
result := models.TradeSet{}
val := ""
switch symbolType {
case 0:
key := fmt.Sprintf(global.TICKER_SPOT, global.EXCHANGE_BINANCE, symbol)
val, _ = helper.DefaultRedis.GetString(key)
case 1:
key := fmt.Sprintf(global.TICKER_FUTURES, global.EXCHANGE_BINANCE, symbol)
val, _ = helper.DefaultRedis.GetString(key)
}
if val != "" {
if err := sonic.Unmarshal([]byte(val), &result); err != nil {
return result, err
}
} else {
return result, errors.New("未找到交易对信息")
}
return result, nil
}
func GetRisk(futApi *FutRestApi, apiInfo *DbModels.LineApiUser, symbol string) []PositionRisk {
for x := 0; x < 5; x++ {
risks, _ := futApi.GetPositionV3(apiInfo, symbol)
if len(risks) > 0 {
return risks
}
time.Sleep(time.Millisecond * 200)
}
return []PositionRisk{}
}
// CancelFutClosePosition 撤销交易对指定方向的平仓单
// apiUserInfo 账户信息
// symbol 交易对
// side 购买反向
// positionSide 仓位方向
// side=BUY positionSide=SHORT 就是撤销平空委托
// side=BUY&positionSide=LONG是开多
// side=SELL&positionSide=LONG是平多
// side=SELL&positionSide=SHORT是开空
// side=BUY&positionSide=SHORT是平空。
func CancelFutClosePosition(apiUserInfo DbModels.LineApiUser, symbol string, side string, positionSide string) error {
//查询当前用户的委托订单
client := GetClient(&apiUserInfo)
resp, _, err := client.SendFuturesRequestAuth("/fapi/v1/openOrders", "GET", map[string]string{
"symbol": symbol,
"recvWindow": "5000",
})
if err != nil {
logger.Error("撤销平仓单时查询委托失败:", err)
return err
}
var openOrders []OpenOrders
sonic.Unmarshal(resp, &openOrders)
orderIdList := make([]int, 0)
for _, order := range openOrders {
if order.Side == side && order.PositionSide == positionSide {
orderIdList = append(orderIdList, order.OrderId)
}
}
// 每次取 10 个元素
batchSize := 10
for i := 0; i < len(orderIdList); i += batchSize {
end := i + batchSize
if end > len(orderIdList) {
end = len(orderIdList) // 避免越界
}
// 取出当前批次的元素
batch := orderIdList[i:end]
marshal, _ := sonic.Marshal(&batch)
_, _, err = client.SendFuturesRequestAuth("/fapi/v1/batchOrders", "DELETE", map[string]string{
"symbol": symbol,
"orderIdList": string(marshal),
"recvWindow": "5000",
})
if err != nil {
return err
}
}
return err
}
// CancelSpotClosePosition 取消现货平仓单(等同于撤销卖单)
// apiUserInfo: Api用户信息
// symbol: 交易对
func CancelSpotClosePosition(apiUserInfo *DbModels.LineApiUser, symbol string) error {
return CancelSpotOrder(symbol, apiUserInfo, "SELL")
}
// 取消现货订单
func CancelSpotOrder(symbol string, apiUserInfo *DbModels.LineApiUser, side string) error {
searchEndpoint := "/api/v3/openOrders"
cencelEndpoint := "/api/v3/order"
searchParams := map[string]interface{}{
"symbol": symbol,
}
client := GetClient(apiUserInfo)
resp, _, err := client.SendSpotAuth(searchEndpoint, "GET", searchParams)
db := GetDBConnection()
if err != nil {
if len(resp) > 0 {
} else {
logger.Error("查询现货当前下单失败:", err)
}
}
var openOrders []map[string]interface{}
err = sonic.Unmarshal(resp, &openOrders)
if err != nil {
return err
}
for _, order := range openOrders {
if orderSymbol, ok := order["symbol"].(string); ok && orderSymbol == symbol {
if orderSide, ok := order["side"].(string); ok && orderSide == side {
orderId, ok := order["orderId"].(float64)
if !ok {
continue
}
orderSn, _ := order["clientOrderId"].(string)
params := map[string]string{
"symbol": orderSymbol,
"orderId": utility.Float64CutString(orderId, 0),
// "cancelRestrictions": "ONLY_NEW",
}
_, _, err = client.SendSpotAuth(cencelEndpoint, "DELETE", params)
if err != nil {
logger.Error("撤销指定现货平仓单失败 ordersn:", orderSn, " orderId:", orderId, " err:", err)
} else {
if err := db.Model(&DbModels.LinePreOrder{}).Where("order_sn = ? and status !='9'", orderSn).Update("status", "4").Error; err != nil {
log.Error("修改止盈单撤销状态失败:", err)
}
}
}
}
return nil
}
return nil
}
// GetTargetSymbol 获取目标交易对信息
func (e *AddPosition) GetTargetSymbol(symbol string, symbolType int) (string, bool, DbModels.LineSymbol, error) {
var targetSymbol string
var notUsdt bool
var symbolInfo DbModels.LineSymbol
// 处理非 USDT 交易对
if !strings.HasSuffix(symbol, "USDT") {
notUsdt = true
if err := e.Db.Model(&DbModels.LineSymbol{}).Where("symbol = ? AND type = ?", symbol, utility.IntToString(symbolType)).Find(&symbolInfo).Error; err != nil {
return "", false, DbModels.LineSymbol{}, err
}
if symbolInfo.Id <= 0 {
return "", false, DbModels.LineSymbol{}, fmt.Errorf("未找到交易对信息")
}
targetSymbol = symbolInfo.BaseAsset + "USDT"
} else {
targetSymbol = symbol
}
return targetSymbol, notUsdt, symbolInfo, nil
}
func (e *AddPosition) GetOrderInfo(req dto.ManuallyCover, symbol, orderType, site, status string) (DbModels.LinePreOrder, error) {
var orderInfo DbModels.LinePreOrder
if err := e.Db.Model(DbModels.LinePreOrder{}).Where("api_id = ? AND symbol = ? AND order_type = ? AND site = ? AND status = ?", req.ApiId, symbol, orderType, site, status).Find(&orderInfo).Error; err != nil {
return DbModels.LinePreOrder{}, err
}
if orderInfo.Id <= 0 {
return DbModels.LinePreOrder{}, fmt.Errorf("未找到主仓信息")
}
return orderInfo, nil
}
func (e *AddPosition) GetFutOrderInfo(req dto.ManuallyCover, symbol, orderType, status string) (DbModels.LinePreOrder, error) {
var orderInfo DbModels.LinePreOrder
if err := e.Db.Model(DbModels.LinePreOrder{}).Where("api_id = ? AND symbol = ? AND order_type = ? AND status = ? AND cover_type = 2", req.ApiId, symbol, orderType, status).Find(&orderInfo).Error; err != nil {
return DbModels.LinePreOrder{}, err
}
if orderInfo.Id <= 0 {
return DbModels.LinePreOrder{}, fmt.Errorf("未找到主仓信息")
}
return orderInfo, nil
}
// GetFutSpotOrderInfo 获取合约对现货的订单信息
func (e *AddPosition) GetFutSpotOrderInfo(req dto.ManuallyCover, symbol, orderType, status string) (DbModels.LinePreOrder, error) {
var orderInfo DbModels.LinePreOrder
if err := e.Db.Model(DbModels.LinePreOrder{}).Where("api_id = ? AND symbol = ? AND order_type = ? AND status = ? AND cover_type = 3", req.ApiId, symbol, orderType, status).Find(&orderInfo).Error; err != nil {
return DbModels.LinePreOrder{}, err
}
if orderInfo.Id <= 0 {
return DbModels.LinePreOrder{}, fmt.Errorf("未找到主仓信息")
}
return orderInfo, nil
}
// CalculateAmount 计算加仓数量
func (e *AddPosition) CalculateAmount(req dto.ManuallyCover, totalNum, lastPrice decimal.Decimal, amountDigit int, notUsdt bool, symbolInfo DbModels.LineSymbol) (decimal.Decimal, error) {
var amt decimal.Decimal
if req.CoverType == 1 {
decimalValue := utility.StringToDecimal(req.Value).Div(decimal.NewFromInt(100))
amt = totalNum.Mul(decimalValue)
} else {
decimalValue := utility.StringToDecimal(req.Value)
if notUsdt {
tickerSymbolMaps := make([]dto.Ticker, 0)
tickerSymbol := helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val()
if err := sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps); err != nil {
return decimal.Zero, err
}
var tickerPrice decimal.Decimal
for _, symbolMap := range tickerSymbolMaps {
if symbolMap.Symbol == strings.ToUpper(symbolInfo.BaseAsset+"USDT") {
tickerPrice, _ = decimal.NewFromString(symbolMap.Price)
break
}
}
for _, symbolMap := range tickerSymbolMaps {
if symbolMap.Symbol == strings.ToUpper(symbolInfo.QuoteAsset+"USDT") {
uTickerPrice, _ := decimal.NewFromString(symbolMap.Price)
div := tickerPrice.Div(decimal.NewFromInt(1).Div(uTickerPrice))
amt = decimalValue.Div(div)
break
}
}
} else {
amt = decimalValue.Div(lastPrice)
}
}
return amt.Truncate(int32(amountDigit)), nil
}
// 主单平仓删除缓存
// mainOrderId 主单id
// coverType 1现货->合约 2->合约->合约 3合约->现货
func MainClosePositionClearCache(mainOrderId int, coverType int) {
if coverType == 1 {
spotStopArray, _ := helper.DefaultRedis.GetAllList(rediskey.SpotStopLossList)
spotAddpositionArray, _ := helper.DefaultRedis.GetAllList(rediskey.SpotAddPositionList)
var position AddPositionList
var stop dto.StopLossRedisList
for _, item := range spotAddpositionArray {
if err := sonic.Unmarshal([]byte(item), &position); err != nil {
log.Error("MainClosePositionClearCache Unmarshal err:", err)
}
if position.Pid == mainOrderId {
helper.DefaultRedis.LRem(rediskey.SpotAddPositionList, item)
}
}
for _, item := range spotStopArray {
if err := sonic.Unmarshal([]byte(item), &stop); err != nil {
log.Error("MainClosePositionClearCache Unmarshal err:", err)
}
if stop.PId == mainOrderId {
helper.DefaultRedis.LRem(rediskey.SpotStopLossList, item)
}
}
} else {
futAddpositionArray, _ := helper.DefaultRedis.GetAllList(rediskey.FuturesAddPositionList)
futStopArray, _ := helper.DefaultRedis.GetAllList(rediskey.FuturesStopLossList)
var position AddPositionList
var stop dto.StopLossRedisList
for _, item := range futAddpositionArray {
if err := sonic.Unmarshal([]byte(item), &position); err != nil {
log.Error("MainClosePositionClearCache Unmarshal err:", err)
}
if position.Pid == mainOrderId {
helper.DefaultRedis.LRem(rediskey.FuturesAddPositionList, item)
}
}
for _, item := range futStopArray {
if err := sonic.Unmarshal([]byte(item), &stop); err != nil {
log.Error("MainClosePositionClearCache Unmarshal err:", err)
}
if stop.PId == mainOrderId {
helper.DefaultRedis.LRem(rediskey.FuturesStopLossList, item)
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,62 @@
package binanceservice
import (
"context"
"fmt"
"go-admin/common/const/rediskey"
"go-admin/common/helper"
"time"
"github.com/bytedance/sonic"
"github.com/go-admin-team/go-admin-core/logger"
)
/*
修改订单信息
*/
func ChangeFutureOrder(mapData map[string]interface{}) {
// 检查订单号是否存在
orderSn, ok := mapData["c"]
if !ok {
logger.Error("合约订单回调失败,没有订单号")
return
}
// 获取数据库连接
db := GetDBConnection()
if db == nil {
logger.Error("合约订单回调失败,无法获取数据库连接")
return
}
// 获取 Redis 锁
lock := helper.NewRedisLock(fmt.Sprintf(rediskey.SpotCallBack, orderSn), 10, 5, 500*time.Millisecond)
acquired, err := lock.AcquireWait(context.Background())
if err != nil {
logger.Error("合约订单回调失败,获取锁失败:", orderSn, " err:", err)
return
}
if !acquired {
logger.Error("合约订单回调失败,获取锁失败:", orderSn)
return
}
defer lock.Release()
// 查询订单
preOrder, err := getPreOrder(db, orderSn)
if err != nil {
logger.Error("合约订单回调失败,查询订单失败:", orderSn, " err:", err)
return
}
// 解析订单状态
status, ok := mapData["X"].(string)
if !ok {
mapStr, _ := sonic.Marshal(&mapData)
logger.Error("订单回调失败,没有状态:", string(mapStr))
return
}
//todo
}

View File

@ -0,0 +1,228 @@
package binanceservice
import (
"errors"
"time"
"github.com/shopspring/decimal"
)
// OrderPlacementService 币安现货下单
type OrderPlacementService struct {
ApiId int `json:"api_id"` //api_id
Symbol string `json:"symbol"` //交易对
Side string `json:"side"` //购买方向
Type string `json:"type"` //下单类型 MARKET=市价 LIMIT=限价 TAKE_PROFIT_LIMIT=限价止盈 STOP_LOSS_LIMIT=限价止损
TimeInForce string `json:"timeInForce"` // 订单有效期,默认为 GTC (Good Till Cancelled)
Price decimal.Decimal `json:"price"` //限价单价
Quantity decimal.Decimal `json:"quantity"` //下单数量
NewClientOrderId string `json:"newClientOrderId"` //系统生成的订单号
StopPrice decimal.Decimal `json:"stopprice"` //止盈止损时需要
Rate string `json:"rate"` //下单百分比
}
func (s *OrderPlacementService) CheckParams() error {
if s.ApiId == 0 || s.Symbol == "" || s.Type == "" || s.NewClientOrderId == "" || s.Side == "" || s.Quantity.LessThan(decimal.Zero) {
return errors.New("缺失下单必要参数")
}
if s.Type == "LIMIT" && s.Price.LessThanOrEqual(decimal.Zero) {
return errors.New("缺失限价单参数price")
}
if s.Type == "TAKE_PROFIT_LIMIT" || s.Type == "STOP_LOSS_LIMIT" {
if s.StopPrice.LessThanOrEqual(decimal.Zero) {
return errors.New("缺失止盈止损订单参数stopprice")
}
}
return nil
}
// CancelOpenOrdersReq 撤销单一交易对的所有挂单
type CancelOpenOrdersReq struct {
ApiId int `json:"api_id"` //api_id
Symbol string `json:"symbol"` //交易对
}
func (r *CancelOpenOrdersReq) CheckParams() error {
if r.Symbol == "" {
return errors.New("缺失下单必要参数")
}
return nil
}
// EntryPriceResult 计算均价结果
type EntryPriceResult struct {
TotalNum decimal.Decimal `json:"total_num"` //总数量
EntryPrice decimal.Decimal `json:"entry_price"` //均价
FirstPrice decimal.Decimal `json:"first_price"` //主单的下单价格
TotalMoney decimal.Decimal `json:"total_money"` //总金额U
FirstId int `json:"first_id"` //主单id
}
type FutOrderPlace struct {
ApiId int `json:"api_id"` //api用户id
Symbol string `json:"symbol"` //合约交易对
Side string `json:"side"` //购买方向
Quantity decimal.Decimal `json:"quantity"` //数量
Price decimal.Decimal `json:"price"` //限价单价
SideType string `json:"side_type"` //现价或者市价
OpenOrder int `json:"open_order"` //是否开启限价单止盈止损
Profit decimal.Decimal `json:"profit"` //止盈价格
StopPrice decimal.Decimal `json:"stopprice"` //止损价格
OrderType string `json:"order_type"` //订单类型市价或限价MARKET(市价单) TAKE_PROFIT_MARKET止盈 STOP_MARKET止损
NewClientOrderId string `json:"newClientOrderId"`
}
func (s FutOrderPlace) CheckParams() error {
if s.ApiId == 0 || s.Symbol == "" || s.OrderType == "" || s.NewClientOrderId == "" || s.Side == "" || s.Quantity.LessThan(decimal.Zero) {
return errors.New("缺失下单必要参数")
}
if s.OrderType == "LIMIT" && s.Price.LessThan(decimal.Zero) {
return errors.New("缺失限价单参数price")
}
if s.OrderType == "TAKE_PROFIT_MARKET" || s.OrderType == "STOP_MARKET" {
if s.StopPrice.LessThanOrEqual(decimal.Zero) || s.Profit.LessThanOrEqual(decimal.Zero) {
return errors.New("缺失止盈止损订单参数stopprice")
}
}
return nil
}
// PositionRisk 用户持仓风险
type PositionRisk struct {
Symbol string `json:"symbol"`
PositionSide string `json:"positionSide"`
PositionAmt string `json:"positionAmt"`
EntryPrice string `json:"entryPrice"`
BreakEvenPrice string `json:"breakEvenPrice"`
MarkPrice string `json:"markPrice"`
UnRealizedProfit string `json:"unRealizedProfit"`
LiquidationPrice string `json:"liquidationPrice"`
IsolatedMargin string `json:"isolatedMargin"`
Notional string `json:"notional"`
MarginAsset string `json:"marginAsset"`
IsolatedWallet string `json:"isolatedWallet"`
InitialMargin string `json:"initialMargin"`
MaintMargin string `json:"maintMargin"`
PositionInitialMargin string `json:"positionInitialMargin"`
OpenOrderInitialMargin string `json:"openOrderInitialMargin"`
Adl int `json:"adl"`
BidNotional string `json:"bidNotional"`
AskNotional string `json:"askNotional"`
UpdateTime int64 `json:"updateTime"`
}
type FutOrderResp struct {
ClientOrderId string `json:"clientOrderId"`
CumQty string `json:"cumQty"`
CumQuote string `json:"cumQuote"`
ExecutedQty string `json:"executedQty"`
OrderId int `json:"orderId"`
AvgPrice string `json:"avgPrice"`
OrigQty string `json:"origQty"`
Price string `json:"price"`
ReduceOnly bool `json:"reduceOnly"`
Side string `json:"side"`
PositionSide string `json:"positionSide"`
Status string `json:"status"`
StopPrice string `json:"stopPrice"`
ClosePosition bool `json:"closePosition"`
Symbol string `json:"symbol"`
TimeInForce string `json:"timeInForce"`
Type string `json:"type"`
OrigType string `json:"origType"`
ActivatePrice string `json:"activatePrice"`
PriceRate string `json:"priceRate"`
UpdateTime int64 `json:"updateTime"`
WorkingType string `json:"workingType"`
PriceProtect bool `json:"priceProtect"`
PriceMatch string `json:"priceMatch"`
SelfTradePreventionMode string `json:"selfTradePreventionMode"`
GoodTillDate int64 `json:"goodTillDate"`
}
type HoldeData struct {
Id int `json:"id"` //主单id
UpdateTime time.Time `json:"updateTime" comment:"最后更新时间"`
Type string `json:"type" comment:"1-现货 2-合约"`
Symbol string `json:"symbol"`
AveragePrice decimal.Decimal `json:"averagePrice" comment:"均价"`
Side string `json:"side" comment:"持仓方向"`
TotalQuantity decimal.Decimal `json:"totalQuantity" comment:"持仓数量"`
TotalBuyPrice decimal.Decimal `json:"totalBuyPrice" comment:"总购买金额"`
PositionIncrementCount int `json:"positionIncrementCount"` //加仓次数
HedgeCloseCount int `json:"hedgeCloseCount" comment:"对冲平仓数量"`
TriggerStatus int `json:"triggerStatus" comment:"触发状态 平仓之后重置 0-未触发 1-触发中 2-触发完成"`
PositionStatus int `json:"positionStatus" comment:"加仓状态 0-未开始 1-未完成 2-已完成 3-失败"`
}
// OpenOrders 挂单信息
type OpenOrders struct {
AvgPrice string `json:"avgPrice"` // 平均成交价
ClientOrderId string `json:"clientOrderId"` // 用户自定义的订单号
CumQuote string `json:"cumQuote"` // 成交金额
ExecutedQty string `json:"executedQty"` // 成交量
OrderId int `json:"orderId"` // 系统订单号
OrigQty string `json:"origQty"` // 原始委托数量
OrigType string `json:"origType"` // 触发前订单类型
Price string `json:"price"` // 委托价格
ReduceOnly bool `json:"reduceOnly"` // 是否仅减仓
Side string `json:"side"` // 买卖方向
PositionSide string `json:"positionSide"` // 持仓方向
Status string `json:"status"` // 订单状态
StopPrice string `json:"stopPrice"` // 触发价,对`TRAILING_STOP_MARKET`无效
ClosePosition bool `json:"closePosition"` // 是否条件全平仓
Symbol string `json:"symbol"` // 交易对
Time int64 `json:"time"` // 订单时间
TimeInForce string `json:"timeInForce"` // 有效方法
Type string `json:"type"` // 订单类型
ActivatePrice string `json:"activatePrice"` // 跟踪止损激活价格, 仅`TRAILING_STOP_MARKET` 订单返回此字段
PriceRate string `json:"priceRate"` // 跟踪止损回调比例, 仅`TRAILING_STOP_MARKET` 订单返回此字段
UpdateTime int64 `json:"updateTime"` // 更新时间
WorkingType string `json:"workingType"` // 条件价格触发类型
PriceProtect bool `json:"priceProtect"` // 是否开启条件单触发保护
PriceMatch string `json:"priceMatch"` //price match mode
SelfTradePreventionMode string `json:"selfTradePreventionMode"` //self trading preventation mode
GoodTillDate int `json:"goodTillDate"` //order pre-set auot cancel time for TIF GTD order
}
// 待触发加仓单
type AddPositionList struct {
Pid int `json:"pid"` //主单id
ApiId int `json:"apiId"` //触发账户id
Symbol string `json:"symbol"` //交易对
Price decimal.Decimal `json:"price"` //触发价
Side string `json:"side"` //买卖方向
AddPositionMainType string `json:"addPositionType"` //A账号加仓类型
AddPositionHedgeType string `json:"addPositionHedgeType"` //B账号加仓类型
SymbolType int `json:"type" comment:"交易对类别 1-现货 2-合约"`
}
// SpotAccountInfo 现货账户信息
type SpotAccountInfo struct {
MakerCommission int `json:"makerCommission"`
TakerCommission int `json:"takerCommission"`
BuyerCommission int `json:"buyerCommission"`
SellerCommission int `json:"sellerCommission"`
CommissionRates struct {
Maker string `json:"maker"`
Taker string `json:"taker"`
Buyer string `json:"buyer"`
Seller string `json:"seller"`
} `json:"commissionRates"`
CanTrade bool `json:"canTrade"`
CanWithdraw bool `json:"canWithdraw"`
CanDeposit bool `json:"canDeposit"`
Brokered bool `json:"brokered"`
RequireSelfTradePrevention bool `json:"requireSelfTradePrevention"`
PreventSor bool `json:"preventSor"`
UpdateTime int `json:"updateTime"`
AccountType string `json:"accountType"`
Balances []struct {
Asset string `json:"asset"`
Free string `json:"free"`
Locked string `json:"locked"`
} `json:"balances"`
Permissions []string `json:"permissions"`
Uid int `json:"uid"`
}

View File

@ -0,0 +1,103 @@
package binanceservice
import (
"go-admin/app/admin/models"
DbModels "go-admin/app/admin/models"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
// 获取订单明细
func GetOrderById(db *gorm.DB, id int) (DbModels.LinePreOrder, error) {
result := DbModels.LinePreOrder{}
if err := db.Model(&result).Where("id =?", id).First(&result).Error; err != nil {
return result, err
}
return result, nil
}
// 获取已开仓的对冲单、对冲加仓单id
// pid主单id
// coverType对冲类型 1-现货对合约 2-合约对合约 3-合约对现货
func GetHedgeOpenOrderIds(db *gorm.DB, pid int, coverType int) ([]int, error) {
result := make([]DbModels.LinePreOrder, 0)
resultIds := make([]int, 0)
query := db.Model(&result)
switch coverType {
case 3:
query = query.Where("pid =? AND order_type in ('7','8','10','11') AND operate_type =1 AND status in ('9','13')", pid)
case 2, 1:
query = query.Where("pid =? AND order_type in ('10','11') AND operate_type =1 AND status ='13'", pid)
}
if err := query.Select("id").Find(&result).Error; err != nil {
return resultIds, err
}
for _, v := range result {
resultIds = append(resultIds, v.Id)
}
return resultIds, nil
}
// 获得对冲单
func GetHedgeOpenOrder(db *gorm.DB, pid int, coverType int) (DbModels.LinePreOrder, error) {
result := DbModels.LinePreOrder{}
orderType := ""
switch coverType {
case 1:
orderType = "7"
case 2, 3:
orderType = "10"
}
if err := db.Model(&result).Where("pid =? AND order_type =? AND operate_type =1", pid, orderType).First(&result).Error; err != nil {
return result, err
}
return result, nil
}
// 获取止损单
func GetStopOrder(db *gorm.DB, pid int) (DbModels.LinePreOrder, error) {
result := DbModels.LinePreOrder{}
if err := db.Model(&result).Where("pid =? AND order_type in ('4','6')", pid).First(&result).Error; err != nil {
return result, err
}
return result, nil
}
// 获取最后一条对冲的下单百分比
func GetStopOrderRate(db *gorm.DB, pid int) (decimal.Decimal, error) {
var result decimal.Decimal
if err := db.Model(&DbModels.LinePreOrder{}).Where("pid =? AND order_type in ('7','10')", pid).
Select("rate").
Order("id DESC").
First(&result).Error; err != nil {
return result, err
}
return result, nil
}
// 获取最后一条对冲
func GetLastStop(db *gorm.DB, pid int) (DbModels.LinePreOrder, error) {
result := models.LinePreOrder{}
if err := db.Model(&result).
Joins("JOIN line_pre_order as o ON o.id = line_pre_order.pid AND o.status in ('9','13')").
Where("line_pre_order.pid =? AND line_pre_order.order_type in ('7','10')", pid).
Order("line_pre_order.id DESC").Select("line_pre_order.*").First(&result).Error; err != nil {
return result, err
}
return result, nil
}

View File

@ -0,0 +1,432 @@
package binanceservice
import (
"context"
"fmt"
"go-admin/app/admin/models"
DbModels "go-admin/app/admin/models"
"go-admin/common/const/rediskey"
"go-admin/common/helper"
"go-admin/pkg/utility"
"strconv"
"strings"
"time"
"github.com/bytedance/sonic"
"github.com/go-admin-team/go-admin-core/logger"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
/*
订单回调
*/
func ChangeSpotOrder(mapData map[string]interface{}) {
// 检查订单号是否存在
orderSn, ok := mapData["c"]
if !ok {
logger.Error("订单回调失败, 没有订单号", mapData)
return
}
// 获取数据库连接
db := GetDBConnection()
if db == nil {
logger.Error("订单回调失败, 无法获取数据库连接")
return
}
// 获取 Redis 锁
lockKey := fmt.Sprintf(rediskey.SpotCallBack, orderSn)
lock := helper.NewRedisLock(lockKey, 200, 5, 100*time.Millisecond)
if err := acquireLock(lock, orderSn); err != nil {
return
}
defer lock.Release()
// 查询订单
preOrder, err := getPreOrder(db, orderSn)
if err != nil {
logger.Error("订单回调失败, 查询订单失败:", orderSn, " err:", err)
return
}
// 解析订单状态
status, ok := mapData["X"]
if !ok {
logMapData(mapData)
return
}
// 更新订单状态
orderStatus, reason := parseOrderStatus(preOrder, status, mapData)
if orderStatus == 0 {
logger.Error("订单回调失败,状态错误:", orderSn, " status:", status, " reason:", reason)
return
}
if err := updateOrderStatus(db, preOrder, orderStatus, reason, true, mapData); err != nil {
logger.Error("修改订单状态失败:", orderSn, " err:", err)
return
}
// 根据订单类型和状态处理逻辑
handleOrderByType(db, preOrder, orderStatus)
}
// 获取 Redis 锁
func acquireLock(lock *helper.RedisLock, orderSn interface{}) error {
acquired, err := lock.AcquireWait(context.Background())
if err != nil {
logger.Error("订单回调失败, 获取锁失败:", orderSn, " err:", err)
return err
}
if !acquired {
logger.Error("订单回调失败, 获取锁失败:", orderSn)
return fmt.Errorf("failed to acquire lock")
}
return nil
}
// 记录 mapData 数据
func logMapData(mapData map[string]interface{}) {
mapStr, _ := sonic.Marshal(&mapData)
logger.Error("订单回调失败, 没有状态:", string(mapStr))
}
// 根据订单类型和状态处理逻辑
func handleOrderByType(db *gorm.DB, preOrder *DbModels.LinePreOrder, orderStatus int) {
switch {
// 主单成交
case preOrder.OrderType == 0 && (orderStatus == 9 || orderStatus == 6):
handleMainOrderFilled(db, preOrder)
//主单取消
case preOrder.OrderType == 0 && preOrder.Pid == 0 && orderStatus == 4:
coin := utility.ReplaceSuffix(preOrder.Symbol, preOrder.QuoteSymbol, "")
removeHoldingCache(preOrder.ApiId, coin, preOrder.Id)
// 止盈成交
case preOrder.OrderType == 1 && (orderStatus == 9 || orderStatus == 6):
handleSpotTakeProfitFilled(db, preOrder)
//主单平仓
case preOrder.OrderType == 3 && orderStatus == 9:
handleMainOrderClosePosition(db, preOrder)
}
}
func handleMainOrderClosePosition(db *gorm.DB, preOrder *DbModels.LinePreOrder) {
panic("unimplemented")
}
func handleSpotTakeProfitFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) {
panic("unimplemented")
}
func removeHoldingCache(i1 int, coin string, i2 int) {
panic("unimplemented")
}
func handleMainOrderFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) {
panic("unimplemented")
}
// 解析订单状态
// 5:委托中 9:完全成交 4:取消
func parseOrderStatus(preOrder *DbModels.LinePreOrder, status interface{}, mapData map[string]interface{}) (int, string) {
reason, _ := mapData["r"].(string)
if strings.ToLower(reason) == "none" {
reason = ""
}
switch status {
case "NEW": // 未成交
return 5, reason
case "FILLED": // 完全成交
if preOrder.OrderType < 3 {
return 6, reason
}
return 9, reason
case "CANCELED", "EXPIRED": // 取消
return 4, reason
default:
return 0, reason
}
}
// 更新订单状态
func updateOrderStatus(db *gorm.DB, preOrder *models.LinePreOrder, status int, reason string, isSpot bool, mapData map[string]interface{}) error {
params := map[string]interface{}{"status": strconv.Itoa(status), "desc": reason}
switch isSpot {
case true:
total := decimal.Zero
totalAmount := decimal.Zero
if totalStr, ok := mapData["Z"].(string); ok {
total, _ = decimal.NewFromString(totalStr)
}
if totalAmountStr, ok := mapData["z"].(string); ok {
totalAmount, _ = decimal.NewFromString(totalAmountStr)
}
//主单 修改单价 和成交数量
if total.Cmp(decimal.Zero) > 0 && totalAmount.Cmp(decimal.Zero) > 0 {
num := totalAmount.Div(decimal.NewFromFloat(100)).Mul(decimal.NewFromFloat(99.8))
params["num"] = num
params["price"] = total.Div(totalAmount)
preOrder.Num = num.String()
}
case false:
status, _ := mapData["X"].(string)
if status == "FILLED" {
num, _ := decimal.NewFromString(mapData["z"].(string))
params["num"] = num.Mul(decimal.NewFromFloat(0.998)).String()
params["price"], _ = mapData["ap"].(string)
preOrder.Num = num.String()
}
}
return db.Model(&DbModels.LinePreOrder{}).Where("order_sn = ? AND status < 6", preOrder.OrderSn).
Updates(params).Error
}
// 主单成交 处理止盈止损订单
func processTakeProfitAndStopLossOrders(db *gorm.DB, preOrder *models.LinePreOrder) {
orders := []models.LinePreOrder{}
if err := db.Model(&DbModels.LinePreOrder{}).Where("pid = ? AND order_type >0 AND status = '0' ", preOrder.Id).Find(&orders).Error; err != nil {
logger.Error("订单回调查询止盈止损单失败:", err)
return
}
spotApi := SpotRestApi{}
num, _ := decimal.NewFromString(preOrder.Num)
for i, order := range orders {
if i >= 2 { // 最多处理 2 个订单
break
}
switch order.OrderType {
case 1: // 止盈
processTakeProfitOrder(db, spotApi, order, num)
case 2: // 止损
processStopLossOrder(db, order)
}
}
}
// 处理止盈订单
func processTakeProfitOrder(db *gorm.DB, spotApi SpotRestApi, order models.LinePreOrder, num decimal.Decimal) {
tradeSet, _ := GetTradeSet(order.Symbol, 0)
if tradeSet.Coin == "" {
logger.Error("获取交易对失败")
return
}
price, _ := decimal.NewFromString(order.Price)
// num, _ := decimal.NewFromString(order.Num)
params := OrderPlacementService{
ApiId: order.ApiId,
Symbol: order.Symbol,
Side: order.Site,
Price: price.Truncate(int32(tradeSet.PriceDigit)),
Quantity: num.Truncate(int32(tradeSet.AmountDigit)),
Type: "TAKE_PROFIT_LIMIT",
TimeInForce: "GTC",
StopPrice: price.Truncate(int32(tradeSet.PriceDigit)),
NewClientOrderId: order.OrderSn,
}
err := spotApi.OrderPlace(db, params)
if err != nil {
for x := 0; x < 5; x++ {
if strings.Contains(err.Error(), "LOT_SIZE") {
break
}
err = spotApi.OrderPlace(db, params)
if err == nil {
break
}
}
}
if err != nil {
logger.Error("现货止盈下单失败:", order.OrderSn, " err:", err)
if err := db.Model(&DbModels.LinePreOrder{}).Where("id = ?", order.Id).
Updates(map[string]interface{}{"status": "2", "desc": err.Error()}).Error; err != nil {
logger.Error("现货止盈下单失败,更新状态失败:", order.OrderSn, " err:", err)
}
} else {
if err := db.Model(&DbModels.LinePreOrder{}).Where("id = ? and status ='0'", order.Id).
Updates(map[string]interface{}{"status": "1", "num": num.String()}).Error; err != nil {
logger.Error("现货止盈下单成功,更新状态失败:", order.OrderSn, " err:", err)
}
}
}
// 处理止损订单
// order 止损单
func processStopLossOrder(db *gorm.DB, order models.LinePreOrder) error {
// var stopOrder models.LinePreOrder
// orderTypes := []string{"4", "6", "9", "12"}
// parentId := order.Id
// if order.Pid > 0 {
// parentId = order.Pid
// }
// if utility.ContainsStr(orderTypes, order.OrderType) {
// var err error
// stopOrder, err = GetStopOrder(db, order.Pid)
// if err != nil {
// logger.Error("查询止损单失败:", err)
// return err
// }
// }
// price, _ := decimal.NewFromString(stopOrder.Price)
// stoploss, _ := decimal.NewFromString(order.Rate)
// if holdeB.Id > 0 {
// _, holdeA := GetHoldeA(stopOrder.Pid)
// var percent decimal.Decimal
// lastPercent, _ := GetStopOrderRate(db, stopOrder.Pid)
// if stopOrder.Site == "BUY" {
// //平仓次数>=最大次数 且余数为0 重新计算触发对冲百分比
// // if holdeA.Id > 0 && holdeB.HedgeCloseCount >= stopOrder.HedgeCloseCount && (holdeB.HedgeCloseCount%stopOrder.HedgeCloseCount == 0) {
// rand := getRand(stopOrder.HedgeTriggerPercent, stopOrder.HedgeTriggerPercentMax, lastPercent, 1)
// percent = decimal.NewFromInt(100).Add(rand).Div(decimal.NewFromInt(100))
// // } else {
// // rate, _ := decimal.NewFromString(stopOrder.Rate)
// // percent = decimal.NewFromInt(100).Add(rate).Div(decimal.NewFromInt(100))
// // }
// } else {
// // if holdeA.Id > 0 && holdeB.HedgeCloseCount >= stopOrder.HedgeCloseCount {
// rand := getRand(stopOrder.HedgeTriggerPercent, stopOrder.HedgeTriggerPercentMax, lastPercent, 1)
// percent = decimal.NewFromInt(100).Sub(rand).Div(decimal.NewFromInt(100))
// // } else {
// // rate, _ := decimal.NewFromString(stopOrder.Rate)
// // percent = decimal.NewFromInt(100).Sub(rate).Div(decimal.NewFromInt(100))
// // }
// }
// stoploss = decimal.NewFromInt(100).Sub(percent.Mul(decimal.NewFromInt(100))).Truncate(2)
// price = holdeA.AveragePrice.Mul(percent)
// }
// tradeset, _ := GetTradeSet(stopOrder.Symbol, 1)
// if tradeset.PriceDigit > 0 {
// price = price.Truncate(int32(tradeset.PriceDigit))
// }
// cache := dto.StopLossRedisList{
// PId: stopOrder.Pid,
// ApiId: stopOrder.ApiId,
// Price: price,
// OrderTye: stopOrder.OrderType,
// Site: stopOrder.Site,
// Symbol: stopOrder.Symbol,
// Stoploss: stoploss,
// }
// stoplossKey := fmt.Sprintf(rediskey.SpotStopLossList)
// cacheVal, _ := sonic.MarshalString(&cache)
// if stopOrder.OrderType == "4" {
// stoplossKey = rediskey.FuturesStopLossList
// }
// stopLossVal, _ := helper.DefaultRedis.GetAllList(stoplossKey)
// for _, itemVal := range stopLossVal {
// if strings.Contains(itemVal, fmt.Sprintf("\"pid\":%v,", stopOrder.Pid)) {
// helper.DefaultRedis.LRem(stoplossKey, itemVal)
// break
// }
// }
// //重新保存待触发对冲单
// if err := helper.DefaultRedis.RPushList(stoplossKey, cacheVal); err != nil {
// logger.Error("B单平仓回调,redis添加止损单失败", err)
// }
return nil
}
// 生成随机数 且不重复
// lastPercent 上一次的百分比
// floatNum 小数点后几位
func getRand(start, end, lastPercent decimal.Decimal, floatNum int) decimal.Decimal {
var rand decimal.Decimal
for x := 0; x < 10; x++ {
rand = utility.DecimalRandom(start, end, floatNum)
if rand.Cmp(lastPercent) != 0 {
break
}
}
return rand
}
func GetSystemSetting(db *gorm.DB) (models.LineSystemSetting, error) {
key := fmt.Sprintf(rediskey.SystemSetting)
val, _ := helper.DefaultRedis.GetString(key)
setting := models.LineSystemSetting{}
if val != "" {
sonic.UnmarshalString(val, &setting)
}
if setting.Id > 0 {
return setting, nil
}
var err error
setting, err = ResetSystemSetting(db)
if err != nil {
return setting, err
}
return setting, nil
}
func ResetSystemSetting(db *gorm.DB) (DbModels.LineSystemSetting, error) {
setting := DbModels.LineSystemSetting{}
if err := db.Model(&setting).First(&setting).Error; err != nil {
return setting, err
}
settVal, _ := sonic.MarshalString(&setting)
if settVal != "" {
if err := helper.DefaultRedis.SetString(rediskey.SystemSetting, settVal); err != nil {
logger.Error("redis添加系统设置失败", err)
}
}
return DbModels.LineSystemSetting{}, nil
}
// NEW
// PENDING_NEW
// PARTIALLY_FILLED
// FILLED
// CANCELED
// PENDING_CANCEL
// REJECTED
// EXPIRED
// EXPIRED_IN_MATCH

View File

@ -0,0 +1,68 @@
package binanceservice
import (
"go-admin/models"
"go-admin/models/spot"
"go-admin/pkg/utility"
"sync"
log "github.com/go-admin-team/go-admin-core/logger"
)
var quoteAssetSymbols = []string{"USDT", "ETH", "BTC", "SOL", "BNB", "DOGE"}
func GetSpotSymbols() (map[string]models.TradeSet, []string, error) {
spotApi := SpotRestApi{}
symbols, err := spotApi.GetExchangeInfo()
tradeSets := make(map[string]models.TradeSet, len(symbols))
if err != nil {
log.Error("获取规范信息失败", err)
return tradeSets, []string{}, err
}
var wg sync.WaitGroup
var mu sync.Mutex // 用于保护 tradeSets 的并发写入
for _, item := range symbols {
if utility.ContainsStr(quoteAssetSymbols, item.QuoteAsset) && item.Status == "TRADING" && item.IsSpotTradingAllowed {
wg.Add(1)
go func(item spot.Symbol) {
defer wg.Done()
tradeSet := models.TradeSet{
Coin: item.BaseAsset,
Currency: item.QuoteAsset,
}
for _, filter := range item.Filters {
switch filter.FilterType {
case "PRICE_FILTER":
tradeSet.PriceDigit = utility.GetPrecision(filter.TickSize)
tradeSet.MinBuyVal = utility.StringAsFloat(filter.MinPrice)
case "LOT_SIZE":
tradeSet.AmountDigit = utility.GetPrecision(filter.StepSize)
tradeSet.MinQty = utility.StringAsFloat(filter.MinQty)
tradeSet.MaxQty = utility.StringAsFloat(filter.MaxQty)
}
}
mu.Lock()
tradeSets[item.Symbol] = tradeSet
mu.Unlock()
}(item)
}
}
wg.Wait() // 等待所有 goroutine 完成
log.Info("初始化交易对")
deleteSymbols, err := spotApi.GetSpotTicker24h(&tradeSets)
if err != nil {
log.Error("初始化币安现货交易对失败", err)
return map[string]models.TradeSet{}, deleteSymbols, err
} else {
log.Info("初始化现货交易对完毕")
return tradeSets, deleteSymbols, err
}
}