1
This commit is contained in:
808
services/binanceservice/binancerest.go
Normal file
808
services/binanceservice/binancerest.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user