package data import ( "context" "encoding/json" "fmt" "github.com/shopspring/decimal" "matchmaking-system/internal/biz/structure" "matchmaking-system/internal/data/memory" "matchmaking-system/internal/data/mq/consumer" "matchmaking-system/internal/data/socket/publicData" "matchmaking-system/internal/data/socket/shareData" "matchmaking-system/internal/data/tradedeal/share" "matchmaking-system/internal/data/tradedeal/virtual" "matchmaking-system/internal/pkg/flags" "matchmaking-system/internal/pkg/logging/applogger" "matchmaking-system/internal/pkg/logging/common" "matchmaking-system/internal/pkg/setting" "matchmaking-system/internal/pkg/utils" "sync" "time" ) /* 港股票行情订阅 1、接收用户港股下单的交易对港股行情 2、将订阅行情写入内存中用于并发计算 3、挂单(市价|限价)缓存队列处理-开仓 4、持仓缓存队列处理-平仓 */ // ShareHkdMessage // @Description: type ShareHkdMessage struct { Symbol string `json:"symbol,omitempty"` // 交易所:股票代码 StockCode string `json:"stock_code,omitempty"` // 股票代码 StockName string `json:"stock_name,omitempty"` // 股票名,可能为空 Price decimal.Decimal `json:"price,omitempty"` // 实时价格 UpDownRate decimal.Decimal `json:"up_down_rate,omitempty"` // 涨跌% UpDown decimal.Decimal `json:"up_down,omitempty"` // 涨跌额 TradeV decimal.Decimal `json:"trade_v,omitempty"` // 技术评级指标值 TradeK string `json:"trade_k,omitempty"` // 技术评级 Vol int64 `json:"vol,omitempty"` // 成交量 TurnoverPriceTotal decimal.Decimal `json:"turnover_price_total,omitempty"` // 成交量*价格 PriceTotal string `json:"price_total,omitempty"` // 总市值 PE string `json:"p_e,omitempty"` // pe Eps string `json:"eps,omitempty"` // EPS EmployeesNumber string `json:"employees_number,omitempty"` // 雇员 Plate string `json:"plate,omitempty"` // 板块 Desc string `json:"desc,omitempty"` // 描述 PriceCode string `json:"price_code,omitempty"` // 价格类型 Country string `json:"country,omitempty"` // 国家 Ts int64 `json:"ts,omitempty"` // 时间 Token string `json:"token,omitempty"` // Token 令牌 } // ShareHkdSymbol // @Description: type ShareHkdSymbol struct { HkdMap chan []byte // 订单交易对 -- 用户下单通知订阅 HkdSetMap chan []byte // 订单交易对 -- 用户盘前|盘后行情订阅 HkdMapSymbol sync.Map // 订阅交易簿 -- 过滤重复 HkdSetMapSymbol sync.Map // 订阅交易簿 -- 过滤重复 } // InitCacheSymbolShareHkd // // @Description: // @param ctx // @param uo func InitCacheSymbolShareHkd(ctx context.Context, uo *Data) { for { idnList, err := GetBotStockHkdListByStockId(ctx, uo) if err != nil { return } for _, value := range idnList { // Write to the redis list hot cache for initializing subscriptions when the program is pulled up checkBool, err := Reds.HExists(context.Background(), setting.MarketShareHkd, value).Result() if err != nil { applogger.Error("%v InitCacheSymbolShareHkd.MarketShareHkd.HExists:%v", common.ErrShareHkd, err) continue } if !checkBool { if err = uo.redisDB.HSet(context.Background(), setting.MarketShareHkd, value, value).Err(); err != nil { applogger.Error("%v InitCacheSymbolShareHkd.MarketShareHkd.HSet:%v", common.ErrShareHkd, err) continue } } if !flags.CheckSetting { applogger.Info("初始化港股票代码:%v", value) } } time.Sleep(10 * time.Second) } } // InitSubscribeShareHkd // // @Description: 港股订阅 // @param ctx // @param uo // @param shareIdn func InitSubscribeShareHkd(shareHkd *ShareHkdSymbol, check bool) { for { var key string if check { key = setting.MarketShareBlkHkd } else { key = setting.MarketShareHkd } listSpots, err := LoadLRangeList(key) if err != nil { applogger.Error("%v InitSubscribeShareHkd.LoadLRangeList:%v", common.ErrShareHkd, err) return } for _, value := range listSpots { // Handling duplicate subscriptions _, ok := shareHkd.HkdMapSymbol.Load(value) if ok { continue } shareHkd.HkdMap <- []byte(value) } time.Sleep(10 * time.Second) } } // InitShareHkdCloseCachePrice // // @Description: // @param shareHkd // @param check func InitShareHkdCloseCachePrice(check bool) { for { var key string if check { key = setting.MarketShareBlkHkd } else { key = setting.MarketShareHkd } listSpots, err := LoadLRangeList(key) if err != nil { applogger.Error("%v InitSubscribeShareHkd.LoadLRangeList:%v", common.ErrShareHkd, err) return } for _, value := range listSpots { SubscribeHkdClosePrice(value) // 处理闭盘数据 } time.Sleep(20 * time.Second) } } // InitShareHkdCloseNewPrice // // @Description: 同步实时行情 // @param check func InitShareHkdCloseNewPrice(check bool) { for { var key string if check { key = setting.MarketShareBlkHkd } else { key = setting.MarketShareHkd } listSpots, err := LoadLRangeList(key) if err != nil { applogger.Error("%v InitShareHkdCloseNewPrice.LoadLRangeList:%v", common.ErrShareHkd, err) time.Sleep(20 * time.Second) continue } for _, value := range listSpots { SubscribeHkdCloseNewPrice(value) } time.Sleep(500 * time.Millisecond) } } // InitShareHkdPriceSetUp // // @Description: 同步插针行情 // @param check func InitShareHkdPriceSetUp(check bool) { for { var key string if check { key = setting.MarketShareBlkHkd } else { key = setting.MarketShareHkd } listSpots, err := LoadLRangeList(key) if err != nil { applogger.Error("%v InitShareHkdPriceSetUp.LoadLRangeList:%v", common.ErrShareHkd, err) time.Sleep(20 * time.Second) continue } for _, value := range listSpots { hkdKey := publicData.SymbolCache(flags.Hkd, value, flags.TradeTypePrice) hkdSetCache := fmt.Sprintf("%v%v:%v", flags.StockTradePrices, flags.ShareHkdMarketType, value) vw, err := GetBeforeAndAfterSetPrice(hkdSetCache) if err != nil || len(vw) == 0 { _ = memory.ShareHkdPriceSetUp.Delete(hkdKey) continue } if err = memory.ShareHkdPriceSetUp.Set(hkdKey, []byte(vw)); err != nil { applogger.Error("%v InitShareHkdPriceSetUp.ShareHkdPriceSetUp:%v", common.ErrShareHkd, err) } } time.Sleep(500 * time.Millisecond) } } // SubscribeHkdHSetCodeList // // @Description: 同步列表缓存 // @param symbolStr // @param check func SubscribeHkdHSetCodeList(symbolStr string, check bool) { var key string if check { key = setting.MarketShareBlkHkd } else { key = setting.MarketShareHkd } // Write to the redis list hot cache for initializing subscriptions when the program is pulled up checkBool, err := Reds.HExists(context.Background(), key, symbolStr).Result() if err != nil { return } if !checkBool { if err := Reds.HSet(context.Background(), key, symbolStr, symbolStr).Err(); err != nil { applogger.Error("%v SubscribeHkdHSetCodeList.%v.HSet:%v", common.ErrShareHkd, key, err) return } } } // SubscribeHkdCloseNewPrice // // @Description: 获取行情价 // @param symbolStr // @param check func SubscribeHkdCloseNewPrice(symbolStr string) { // 获取市场真实开闭盘时间 timeValue, checkWeekday := MarketCheckOpenAndCloseTime(flags.ShareHkdMarketType) // 判定是否盘中 if utils.CheckInPlateTime(timeValue) && checkWeekday { // 盘中实时价格 realTimePrice, err := Reds.HGet(context.Background(), flags.ShareHongKongClosingNewPriceKey, symbolStr).Result() if err != nil { realTimePrice = decimal.Zero.String() } if realTimePrice != decimal.Zero.String() { keys := publicData.SymbolCache(flags.Hkd, symbolStr, flags.TradeTypePrice) if err = memory.ShareHkdCache.Set(keys, []byte(realTimePrice)); err != nil { applogger.Error("%v SubscribeHkdCloseNewPrice.ShareHkdCache:%v", common.ErrShareHkd, err) } } } } // SubscribeHkdClosePrice // // @Description: 获取闭盘价 // @param symbolStr func SubscribeHkdClosePrice(symbolStr string) { var err error var sub = decimal.Zero var closeNewPrice, closingPrice, price string closeNewPrice, err = Reds.HGet(context.Background(), flags.ShareHongKongClosingNewPriceKey, symbolStr).Result() if err != nil || closeNewPrice == decimal.Zero.String() { closeNewPrice, err = Reds.HGet(context.Background(), flags.ShareHongKongClosingPriceKey, symbolStr).Result() if err != nil || closeNewPrice == decimal.Zero.String() { closeNewPrice = decimal.Zero.String() } } // 写入闭盘价格 closingPrice, err = Reds.HGet(context.Background(), flags.ShareHongKongBeforeClose, symbolStr).Result() if err != nil { closingPrice = decimal.Zero.String() } // 判定取值数据 if closeNewPrice != decimal.Zero.String() { price = closeNewPrice } else { price = closingPrice } closeKey := publicData.SymbolCache(flags.Hkd, symbolStr, flags.TradeTypeClosePrice) if err = memory.ShareHkdClosePrice.Set(closeKey, []byte(price)); err != nil { applogger.Error("%v ShareHkdClosePrice.Set.price:%v", common.ErrShareHkd, err) } // 写入涨跌幅价格判定缓存 cn := closeNewPrice != decimal.Zero.String() cp := closingPrice != decimal.Zero.String() lcn := utils.IsNumber(closeNewPrice) lcp := utils.IsNumber(closingPrice) if cn && cp && lcn && lcp { sub = decimal.RequireFromString(closingPrice).Sub(decimal.RequireFromString(closeNewPrice)) } chgKey := publicData.SymbolCache(flags.Hkd, symbolStr, flags.TradeTypeChg) // 涨跌幅标识Key if err = memory.ShareHkdChgMark.Set(chgKey, []byte(sub.String())); err != nil { applogger.Error("%v ShareHkdChgMark.Set.price:%v", common.ErrShareHkd, err) } } // OrderSubAdminShareHkdSubscribeBySum // // @Description: 持仓订单浮动盈亏计算 // @param ctx // @param uo func OrderSubAdminShareHkdSubscribeBySum(uo *Data) { for { topIc := setting.AdminShareHkdSubscribe hashMap, err := uo.redisDB.HGetAll(context.Background(), topIc).Result() if err != nil { applogger.Error("OrderSubAdminShareHkdSubscribeBySum.HGetAll:%v", err) time.Sleep(5 * time.Second) continue } var hashList []share.ShareHkdTallyCache for _, value := range hashMap { var msg share.ShareHkdTallyCache if err = json.Unmarshal([]byte(value), &msg); err != nil { time.Sleep(5 * time.Second) continue } switch msg.Status { case flags.Entrust: // 挂单 case flags.Position: // 持仓 hashList = append(hashList, msg) default: // 平仓|撤单 err = uo.redisDB.HDel(context.Background(), topIc, msg.OrderId).Err() if err != nil { applogger.Error("OrderSubAdminShareHkdSubscribeBySum.HDel:%v", err) continue } } } applogger.Info("港股数据量统计:%v", len(hashList)) // 统计印尼股总持仓订单浮动盈亏 priceSum := decimal.Zero for _, value := range hashList { orderModel := shareData.ShareHkdOrderProcessing(topIc, 1, value) if orderModel != nil { var subPrice decimal.Decimal newPrice, err := decimal.NewFromString(orderModel.Price) if err != nil { continue } openPrice, err := decimal.NewFromString(orderModel.OpenPrice) if err != nil { continue } switch value.Order.TradeType { case flags.TradeTypeBuy: // 买涨 = 新价格 - 开仓价 subPrice = newPrice.Sub(openPrice) case flags.TradeTypeSell: // 买跌 = 开仓价 - 新价格 subPrice = openPrice.Sub(newPrice) default: subPrice = decimal.Zero } price := subPrice.Mul(decimal.RequireFromString(value.Order.OrderNumber)) priceSum = priceSum.Add(price) // 累加盈亏统计 } } // 写入缓存 if err = memory.ShareHkdFloating.Set(flags.FloatingHkd, []byte(priceSum.String())); err != nil { applogger.Error("统计港股持仓订单浮动盈亏错误:%v", err) time.Sleep(5 * time.Second) continue } applogger.Info("统计港股持仓订单浮动盈亏:%v", priceSum) time.Sleep(1 * time.Second) } } // SubscribeShareHkd // // @Description: TODO: 港股订阅 // @param ctx // @param uo // @param shareIdn func SubscribeShareHkd(ctx context.Context, shareHkd *ShareHkdSymbol, check bool) { for { select { case symbol, _ := <-shareHkd.HkdMap: symbolStr := string(symbol) topIc := fmt.Sprintf("%v.%v", symbolStr, flags.CountryHkd) _, ok := shareHkd.HkdMapSymbol.Load(symbolStr) if ok { time.Sleep(5 * time.Second) continue } // Transaction determination var key string if check { key = setting.MarketShareBlkHkd // 大宗股交易 } else { key = setting.MarketShareHkd // 港股交易 } // Write to the redis list hot cache for initializing subscriptions when the program is pulled up checkBool, err := Reds.HExists(context.Background(), key, symbolStr).Result() if err != nil { time.Sleep(5 * time.Second) continue } if !checkBool { if err = Reds.HSet(context.Background(), key, symbolStr, symbolStr).Err(); err != nil { applogger.Error("%v SubscribeShareHkd.%v.HSet:%v", common.ErrShareHkd, key, err) time.Sleep(5 * time.Second) continue } } // 处理订阅实时行情数据 go func() { symbolStrCache := symbolStr pubSub := Reds.Subscribe(ctx, topIc) defer pubSub.Close() if _, err = pubSub.Receive(ctx); err != nil { shareHkd.HkdMapSymbol.Delete(symbolStrCache) return } ch := pubSub.Channel() for { select { case msg, _ := <-ch: var subMsg ShareHkdMessage if err = json.Unmarshal([]byte(msg.Payload), &subMsg); err != nil { applogger.Error("%v SubscribeShareHkd.Unmarshal:%v", common.ErrShareHkd, err) time.Sleep(5 * time.Second) continue } SubscribeHkdPrice(subMsg.Price.String(), symbolStr, flags.RealTime) default: Reds.Ping(ctx) // 如果没有消息,发送 PING 命令以维护连接 time.Sleep(10 * time.Second) } } }() // 监控盘前|盘后设置价格 _, okc := shareHkd.HkdSetMapSymbol.Load(symbolStr) if !okc { go func() { for { keyCache := fmt.Sprintf("%v%v:%v", flags.StockTradePrices, flags.ShareHkdMarketType, symbolStr) vw, err := GetBeforeAndAfterSetPrice(keyCache) if err != nil || len(vw) == 0 { priceCache := publicData.SymbolCache(flags.Hkd, symbolStr, flags.TradeTypePrice) memory.ShareHkdPriceSetUp.Delete(priceCache) time.Sleep(15 * time.Second) continue } SubscribeHkdPrice(vw, symbolStr, flags.SetUp) time.Sleep(10 * time.Second) } }() // Write cache to determine whether to repeatedly open the protocol shareHkd.HkdSetMapSymbol.Store(symbolStr, symbolStr) } // Write in the map to determine whether to repeat subscription shareHkd.HkdMapSymbol.Store(symbolStr, symbolStr) } } } // SubscribeHkdPrice // // @Description: TODO: 订阅获取港股实时价格或者[盘前|盘后]设置价格 // @param setPrice // @param symbolStr // @param check func SubscribeHkdPrice(setPrice, symbolStr string, check int) { var err error var sub = decimal.Zero yClosingPrice, err := Reds.HGet(context.Background(), flags.ShareHongKongClosingPriceKey, symbolStr).Result() if err != nil || yClosingPrice == decimal.Zero.String() { yClosingPrice, err = Reds.HGet(context.Background(), flags.ShareHongKongClosingNewPriceKey, symbolStr).Result() if err != nil || yClosingPrice == decimal.Zero.String() { yClosingPrice = setPrice } } // 计算涨跌幅并写入内存缓存 yc := yClosingPrice != decimal.Zero.String() sp := setPrice != decimal.Zero.String() lyc := utils.IsNumber(yClosingPrice) lsp := utils.IsNumber(setPrice) if yc && sp && lyc && lsp { sub = decimal.RequireFromString(setPrice).Sub(decimal.RequireFromString(yClosingPrice)) } // 涨跌幅标识Key chgKey := publicData.SymbolCache(flags.Hkd, symbolStr, flags.TradeTypeChg) // 涨跌幅标识Key if err = memory.ShareHkdChgMark.Set(chgKey, []byte(sub.String())); err != nil { applogger.Error("%v SubscribeHkdPrice.ShareHkdChgMark.Set.price:%v", common.ErrShareHkd, err) } if !flags.CheckSetting { applogger.Debug("涨跌幅交易对内存key:%v,涨跌幅数值:%v", symbolStr, sub) } // 交易类型:0最新价,1买入,2卖出 price := publicData.SymbolCache(flags.Hkd, symbolStr, flags.TradeTypePrice) switch check { case flags.RealTime: // 实时价格(优先级高) if err = memory.ShareHkdCache.Set(price, []byte(setPrice)); err != nil { applogger.Error("%v SubscribeShareHkd.ShareHkdCache.Set.price:%v", common.ErrShareHkd, err) } case flags.SetUp: // 设置[盘前|盘后]价格 if err = memory.ShareHkdPriceSetUp.Set(price, []byte(setPrice)); err != nil { applogger.Error("%v SubscribeShareHkd.ShareHkdPriceSetUp:%v", common.ErrShareHkd, err) } } if !flags.CheckSetting { applogger.Debug("股票交易对内存Key:%v,最新价格:%v", price, setPrice) } } // SubscribeHkdSetForcedClosure // // @Description: TODO: 处理港股强平阈值 // @param symbolStr func SubscribeHkdSetForcedClosure(symbolStr string) { flatRatio := GetCacheForcedClosure(flags.StockHKDSystemSetUpKey, symbolStr) chgKey := publicData.SymbolCache(flags.Hkd, symbolStr, flags.TradeTypeForcedClosure) if err := memory.ShareHkdForcedClosure.Set(chgKey, []byte(flatRatio.String())); err != nil { applogger.Error("%v SubscribeHkdSetForcedClosure.StockHKDSystemSetUpKey err:%v", common.ErrShareHkd, err) return } } // ShareHkdTransactionEntrust // // @Description: 港股委托订单 // @param ctx func ShareHkdTransactionEntrust(ctx context.Context) { for { ShareHkdTransactionCalculationEntrust(ctx) time.Sleep(600 * time.Millisecond) } } // ShareHkdTransactionCalculationEntrust // // @Description: 港股挂单缓存队列计算 // @param ctx func ShareHkdTransactionCalculationEntrust(ctx context.Context) { var wg sync.WaitGroup // 全局变量处理 if CheckGlobalTread(flags.HkdMarket) { time.Sleep(5 * time.Second) return } // 获取挂单缓存列表 orderMap, err := Reds.HGetAll(context.Background(), setting.MarketShareHkdEntrust).Result() if err != nil { applogger.Error("%v ShareHkdTransactionCalculationEntrust.HGetAll:%v", common.ErrShareHkd, err) return } for _, value := range orderMap { var msg = make(chan share.ShareHkdTallyCache, 1) var entrust share.ShareHkdTallyCache if err = json.Unmarshal([]byte(value), &entrust); err != nil { applogger.Error("%v ShareHkdTransactionCalculationEntrust.Unmarshal:%v", common.ErrShareHkd, err) continue } // 股票实时价格 var newPrice decimal.Decimal newPrice, err = GetShareHkdPrice(entrust.Symbol) if err != nil { applogger.Warn("%v ShareHkdTransactionCalculationEntrust.Get:%v---%v", common.ErrShareHkd, entrust.Symbol, err) continue } // 判定涨跌幅 chg := GetShareChgHkd(flags.Hkd, entrust.Symbol) // 涨跌幅标识 if share.CheckOrderHkdByChg(setting.MarketShareHkdEntrust, entrust, chg) { //applogger.Warn("当前股票%v订单在挂单状态已达到涨跌幅上限.", entrust.Symbol) continue } wg.Add(1) go func(value string) { var resultMsg share.ShareHkdTallyCache switch entrust.Order.DealType { case flags.DealTypeLimited: // 限价挂单 resultMsg = ShareHkdConcurrentComputingEntrustLimited(&entrust, newPrice, &wg) case flags.DealTypeMarket: // 市价挂单(开盘即成交) resultMsg = ShareHkdConcurrentComputingEntrustMarket(&entrust, newPrice, &wg) } msg <- resultMsg // 通知管道异步处理订单操作 }(value) // 处理并发结果(需要改成通过信道通知) select { case resultMsg, _ := <-msg: switch resultMsg.Status { case flags.Entrust: // 挂单中 if !flags.CheckSetting { applogger.Debug("从信道接收港股挂单信息:%v", resultMsg) } case flags.Position: // 持仓中 if !flags.CheckSetting { applogger.Debug("从信道接收到港股持仓信息:%v", resultMsg) } // 开盘逻辑 if err = ShareHkdLiquidationEntrust(ctx, &resultMsg); err != nil { applogger.Error("%v ShareHkdTransactionCalculationEntrust.ShareHkdLiquidationEntrust:%v", common.ErrShareHkd, err) continue } // 写入持仓缓存列表 var byteStr []byte byteStr, err = json.Marshal(resultMsg) if err != nil { applogger.Error("%v ShareHkdTransactionCalculationEntrust.Marshal:%v", common.ErrShareHkd, err) continue } // TODO: 写入持仓消息队列 //if err = uo.mqProducer.Entrust.PushNotice(byteStr); err != nil { // applogger.Error("%v ShareHkdTransactionCalculationEntrust.EntrustPushNotice:%v", common.ErrShareHkd, err) // continue //} // 写入持仓队列缓存 if err = Reds.HSet(context.Background(), setting.MarketShareHkdPosition, resultMsg.OrderId, string(byteStr)).Err(); err != nil { continue } } } } wg.Wait() applogger.Info("港股挂单并发计算执行完毕......") } // ShareHkdConcurrentComputingEntrustLimited // // @Description: 港股挂单(限价|市价)缓存列表业务处理 // @param order // @param newPrice // @param bidPrice // @param askPrice // @param wg // @return tradedeal.ShareHkdTallyCache func ShareHkdConcurrentComputingEntrustLimited(order *share.ShareHkdTallyCache, newPrice decimal.Decimal, wg *sync.WaitGroup) share.ShareHkdTallyCache { var deleteCache bool var dealPrice string var limitPrice = decimal.RequireFromString(order.Order.LimitPrice) // 限价-价格 var status = order.Status // 原始价格 switch order.Order.TradeType { case flags.TradeTypeBuy: // 买涨 if !flags.CheckSetting { applogger.Debug("用户ID:%v,限价买入下单价格:%v,最新市价:%v,判定结果:%v,原始状态:%v", order.UserId, limitPrice, newPrice, limitPrice.Cmp(newPrice), order.Status) } // 限价买入逻辑判定 -> 挂单金额 > 当前价格 if limitPrice.Cmp(newPrice) > 0 { deleteCache = true difference := newPrice.Mul(utils.Difference()) // 设置价差 dealPrice = newPrice.Add(difference).String() // 开仓价格 } case flags.TradeTypeSell: // 买跌 if !flags.CheckSetting { applogger.Debug("用户ID:%v,限价卖出下单价格:%v,最新市价:%v,判定结果:%v,原始状态:%v", order.UserId, limitPrice, newPrice, limitPrice.Cmp(newPrice), order.Status) } // 限价卖入逻辑判定 -> 挂单金额 < 当前价格 if limitPrice.Cmp(newPrice) < 0 { deleteCache = true difference := newPrice.Mul(utils.Difference()) // 设置价差 dealPrice = newPrice.Sub(difference).String() // 开仓价格 } } if deleteCache { order.Status = flags.Position if err := Reds.HDel(context.Background(), setting.MarketShareHkdEntrust, order.OrderId).Err(); err != nil { applogger.Error("%v ShareHkdConcurrentComputingEntrustLimited.HDel:%v", common.ErrShareHkd, err) order.Status = status } } wg.Done() return share.ShareHkdTallyCache{ UserId: order.UserId, // 用户Id OrderId: order.OrderId, // 订单Id Symbol: order.Symbol, // 下单交易对 OpenPrice: dealPrice, // 开仓价格 Status: order.Status, // 订单状态 Order: order.Order, // 下单信息 ClosingTime: order.ClosingTime, // 平仓时间 } } // ShareHkdConcurrentComputingEntrustMarket // // @Description: 港股市价下单进入挂单队列(休市) // @param order // @param newPrice // @param bidPrice // @param askPrice // @param wg // @return tradedeal.ShareHkdTallyCache func ShareHkdConcurrentComputingEntrustMarket(order *share.ShareHkdTallyCache, newPrice decimal.Decimal, wg *sync.WaitGroup) share.ShareHkdTallyCache { var deleteCache bool var dealPrice string var marketPrice = decimal.RequireFromString(order.Order.MarketPrice) // 市价-价格 var status = order.Status // 原始价格 switch order.Order.TradeType { case flags.TradeTypeBuy: // 买涨 if !flags.CheckSetting { applogger.Debug("用户ID:%v,限价买入下单价格:%v,最新市价:%v,原始状态:%v", order.UserId, marketPrice, newPrice, order.Status) } deleteCache = true difference := newPrice.Mul(utils.Difference()) // 设置价差 dealPrice = newPrice.Add(difference).String() // 开仓价格 case flags.TradeTypeSell: // 买跌 if !flags.CheckSetting { applogger.Debug("用户ID:%v,限价卖出下单价格:%v,最新市价:%v,原始状态:%v", order.UserId, marketPrice, newPrice, order.Status) } deleteCache = true difference := newPrice.Mul(utils.Difference()) // 设置价差 dealPrice = newPrice.Sub(difference).String() // 开仓价格 } if deleteCache { order.Status = flags.Position if err := Reds.HDel(context.Background(), setting.MarketShareHkdEntrust, order.OrderId).Err(); err != nil { applogger.Error("%v ShareHkdConcurrentComputingEntrustMarket.HDel:%v", common.ErrShareHkd, err) order.Status = status } } wg.Done() return share.ShareHkdTallyCache{ UserId: order.UserId, // 用户Id OrderId: order.OrderId, // 订单Id Symbol: order.Symbol, // 下单交易对 OpenPrice: dealPrice, // 开仓价格 Status: order.Status, // 订单状态 Order: order.Order, // 下单信息 ClosingTime: order.ClosingTime, // 平仓时间 } } // ShareBlockHkdConcurrentComputingEntrustMarket // // @Description: 大宗(港股)交易 // @param order // @param newPrice // @param wg // @return share.ShareHkdTallyCache func ShareBlockHkdConcurrentComputingEntrustMarket(order *share.ShareHkdTallyCache, newPrice decimal.Decimal, wg *sync.WaitGroup) share.ShareHkdTallyCache { var status = order.Status // 原始状态 order.Status = flags.Position if err := Reds.HDel(context.Background(), setting.MarketShareBlkEntrust, order.OrderId).Err(); err != nil { applogger.Error("%v ShareBlockHkdConcurrentComputingEntrustMarket.HDel:%v", common.ErrShareBlk, err) order.Status = status } wg.Done() return share.ShareHkdTallyCache{ UserId: order.UserId, // 用户Id OrderId: order.OrderId, // 订单Id Symbol: order.Symbol, // 下单交易对 OpenPrice: newPrice.String(), // 开仓价格 Status: order.Status, // 订单状态 Order: order.Order, // 下单信息 ClosingTime: order.ClosingTime, // 平仓时间 } } // ShareHkdMqOpenBusiness // // @Description: TODO: 港股持仓消息队列 // @param ctx // @param uo func ShareHkdMqOpenBusiness(ctx context.Context, uo *Data) { go func() { uo.mqConsumer.Entrust.ConsumerNotice() }() for { select { case message, ok := <-consumer.ConsumerEntrustMq.ShareHkdMq: if !ok { time.Sleep(5 * time.Second) applogger.Error("%v ShareHkdMqOpenBusiness.ShareIdnMq err....", common.ErrShareHkd) continue } // 持仓订单缓存数据序列化解析 var resultMsg share.ShareHkdTallyCache if err := json.Unmarshal(message, &resultMsg); err != nil { applogger.Error("%v ShareHkdMqOpenBusiness.Unmarshal:%v", common.ErrShareHkd, err) time.Sleep(5 * time.Second) continue } // 持仓平仓 if err := ShareHkdLiquidationEntrust(ctx, &resultMsg); err != nil { applogger.Error("%v ShareHkdMqOpenBusiness.ShareHkdLiquidationEntrust:%v", common.ErrShareHkd, err) continue } default: // TODO: 是否处理空队列缓存的情况 applogger.Error("这里没有监控到持仓mq队列消息.........") time.Sleep(2 * time.Second) } } } // ShareHkdLiquidationEntrust // // @Description: 港股挂单-->开仓业务处理 // @param ctx // @param order // @return error func ShareHkdLiquidationEntrust(ctx context.Context, order *share.ShareHkdTallyCache) error { // 1、开盘 err := StockHkdOpenOrder(ctx, Msql, order.UserId, order.OrderId, order.OpenPrice, order.Order) if err != nil { applogger.Error("%v ShareHkdLiquidationEntrust.StockIdnOpenOrder:%v", common.ErrShareHkd, err) return err } // 大宗(港股)交易判定 var entrustKey, adminKey string if !CheckTypeStatus(order.Order.Type) { entrustKey = setting.ShareHkdSubscribe adminKey = setting.AdminShareHkdSubscribe } else { entrustKey = setting.ShareBlkSubscribe adminKey = setting.AdminShareBlkSubscribe } // 2、更新用户订阅缓存列表订单状态 updateKey := virtual.OrderIdListKey(entrustKey, order.UserId) if err = UpdateShareHkdSubscribeHashStatusByOrderId(order.OrderId, updateKey, flags.Position, order.OpenPrice); err != nil { applogger.Error("%v ShareHkdLiquidationEntrust.Entrust.%v:%v", common.ErrShareHkd, entrustKey, err) return err } // 3、更新管理员订阅缓存列表订单状态 if err = UpdateShareHkdSubscribeHashStatusByOrderId(order.OrderId, adminKey, flags.Position, order.OpenPrice); err != nil { applogger.Error("%v ShareHkdLiquidationEntrust.Entrust.%v:%v", common.ErrShareHkd, adminKey, err) return err } return nil } // ShareHkdTransactionPosition // // @Description: 港股持仓订单 // @param ctx func ShareHkdTransactionPosition(ctx context.Context) { for { ShareHkdTransactionCalculationPosition(ctx) time.Sleep(600 * time.Millisecond) } } // ShareHkdTransactionCalculationPosition // // @Description: 港股持仓缓存队列计算 // @param ctx func ShareHkdTransactionCalculationPosition(ctx context.Context) { var wg sync.WaitGroup // 判定是否交易 if CheckGlobalTread(flags.HkdMarket) { time.Sleep(5 * time.Second) return } // TODO: 获取IPO未支付订单缓存 userIdMap, _ := GetBotUserArrears(ctx) // 获取持仓缓存列表 orderMap, err := Reds.HGetAll(context.Background(), setting.MarketShareHkdPosition).Result() if err != nil { applogger.Error("%v ShareHkdTransactionCalculationPosition.HGetAll:%v", common.ErrShareHkd, err) return } for _, value := range orderMap { var msg = make(chan share.ShareHkdTallyCache, 1) var entrust share.ShareHkdTallyCache if err = json.Unmarshal([]byte(value), &entrust); err != nil { applogger.Error("%v ShareHkdTransactionCalculationPosition.Unmarshal:%v", common.ErrShareHkd, err) continue } // TODO: 判定是否未IPO未支付订单-不能平仓 _, isOk := userIdMap[entrust.UserId] if isOk { continue } // 处理时间 if !CheckShareSystemTime(entrust.ClosingTime) { applogger.Info("港股:%v,平仓时间:%v暂未到达...", entrust.Symbol, entrust.ClosingTime) continue } // TODO: 实时获取强平阈值 //entrust.Order.System.StrongFlatRatio = GetForcedClosureValue(flags.Hkd, entrust.Symbol) // 印尼股实时价格 var newPrice decimal.Decimal newPrice, err = GetShareHkdPrice(entrust.Symbol) if err != nil { applogger.Warn("%v ShareHkdTransactionCalculationPosition.Get:%v--%v", common.ErrShareHkd, entrust.Symbol, err) continue } // 判定涨跌幅 chg := GetShareChgHkd(flags.Hkd, entrust.Symbol) if share.CheckOrderHkdByChg(setting.MarketShareHkdEntrust, entrust, chg) { //applogger.Warn("当前股票%v订单在挂单状态已达到涨跌幅上限.", entrust.Symbol) continue } // 判定配额强平 checkFlattening := GetStrongFlatteningThreshold(entrust.UserId) wg.Add(1) go func(value string) { resultMsg := ShareHkdConcurrentComputingPosition(&entrust, newPrice, &wg, checkFlattening) msg <- resultMsg }(value) // 处理并发结果 select { case resultMsg, _ := <-msg: switch resultMsg.Status { case flags.Position: // 持仓 if !flags.CheckSetting { applogger.Debug("从信道接收到港股持仓信息:%v", resultMsg) } case flags.Close: // 平仓 if !flags.CheckSetting { applogger.Debug("从信道接收到港股平仓信息:%v", resultMsg) } // TODO: 平仓操作(优化写入队列中等待平仓) //var byteStr []byte //byteStr, err = json.Marshal(resultMsg) //if err != nil { // applogger.Error("%v ShareHkdTransactionCalculationPosition.Marshal:%v", common.ErrShareHkd, err) // continue //} //if err = uo.mqProducer.Position.PushNotice(byteStr); err != nil { // applogger.Error("%v ShareHkdTransactionCalculationPosition.DealPublish:%v", common.ErrShareHkd, err) // continue //} if err = ShareHkdLiquidationPosition(ctx, &resultMsg); err != nil { applogger.Error("%v ShareHkdTransactionCalculationPosition.ShareHkdLiquidationPosition:%v", common.ErrShareHkd, err) continue } } } } wg.Wait() applogger.Info("港股持仓并发计算执行完毕......") } // ShareHkdConcurrentComputingPosition // // @Description: 港股持仓缓存列表业务处理 // @param order // @param newPrice // @param bidPrice // @param askPrice // @param wg // @return tradedeal.ShareHkdTallyCache func ShareHkdConcurrentComputingPosition(order *share.ShareHkdTallyCache, newPrice decimal.Decimal, wg *sync.WaitGroup, checkFlattening bool) share.ShareHkdTallyCache { var deleteCache, checkBool bool var dealPrice string var sellShort decimal.Decimal var openPrice = decimal.RequireFromString(order.OpenPrice) // 限价 var orderNumber = decimal.RequireFromString(order.Order.OrderNumber) // 仓位 var pryNum decimal.Decimal lenPryNum := len(utils.ReplaceStr(order.Order.PryNum)) == 0 if lenPryNum || order.Order.PryNum == flags.SetZero || order.Order.System.UserStatus != flags.SetThree || !utils.IsNumberInt(order.Order.PryNum) { pryNum = decimal.RequireFromString(flags.SetOne) } else { pryNum = decimal.RequireFromString(order.Order.PryNum) } // 下单判定设置(false无设置|true止盈止损) _, stopWinPrice, stopLossPrice, _ := StockHkdVoteStopType(order.Order) switch order.Order.TradeType { case flags.TradeTypeBuy: // 买涨(强平) if !flags.CheckSetting { applogger.Debug("用户ID:%v,持仓开仓价格:%v,最新市价:%v,止盈:%v,止损:%v,原始状态:%v", order.UserId, openPrice, newPrice, stopWinPrice, stopLossPrice, order.Status) } // 买涨:挂止损止盈,止盈价必须<当前价,止损价必须>当前价; if !stopWinPrice.IsZero() { if stopWinPrice.Cmp(newPrice) < 0 { checkBool = true difference := newPrice.Mul(utils.Difference()) // 设置价差 dealPrice = newPrice.Sub(difference).String() // 平仓价格 } } if !stopLossPrice.IsZero() { if stopLossPrice.Cmp(newPrice) > 0 { checkBool = true difference := newPrice.Mul(utils.Difference()) // 设置价差 dealPrice = newPrice.Add(difference).String() // 平仓价格 } } // 强行平仓(做多) if !checkBool { sellShort = newPrice.Sub(openPrice).Mul(orderNumber) } case flags.TradeTypeSell: // 买跌(存在强行平仓) if !flags.CheckSetting { applogger.Debug("用户ID:%v,限价持仓买跌下单价格:%v,最新市价:%v,止盈:%v,止损:%v,原始状态:%v", order.UserId, openPrice, newPrice, stopWinPrice, stopLossPrice, order.Status) } // 买跌:挂止损止盈,止盈价必须>当前价,止损价必须<当前价; if !stopWinPrice.IsZero() { if stopWinPrice.Cmp(newPrice) > 0 { checkBool = true difference := newPrice.Mul(utils.Difference()) // 设置价差 dealPrice = newPrice.Add(difference).String() // 平仓价格 } } if !stopLossPrice.IsZero() { if stopLossPrice.Cmp(newPrice) < 0 { checkBool = true difference := newPrice.Mul(utils.Difference()) // 设置价差 dealPrice = newPrice.Sub(difference).String() // 平仓价格 } } // 强行平仓(做空) if !checkBool { sellShort = openPrice.Sub(newPrice).Mul(orderNumber) } } /* 强行平仓(做多|做空) 1、做多:(最新成交价-开仓成交价)*仓位 2、做空:(开仓成交价-最新成交价)*仓位 3、当浮动盈亏 >= 保证金的70%订单强行平仓 */ if sellShort.IsNegative() && checkFlattening { orderAmount := newPrice.Mul(decimal.RequireFromString(order.Order.OrderNumber)).Div(pryNum) // 保证金 = 当前价格 * 订单数量 / 杠杆 floatPrice := orderAmount.Mul(decimal.RequireFromString(order.Order.System.StrongFlatRatio)) if !flags.CheckSetting { applogger.Debug("强平保证金:%v,强平浮动70:%v,强行平仓判定:%v", orderAmount, floatPrice, sellShort.Abs().Cmp(floatPrice)) } if sellShort.Abs().Cmp(floatPrice) >= 0 { checkBool = true // 设置价差 difference := newPrice.Mul(utils.Difference()) // 设置价差 switch order.Order.TradeType { case flags.TradeTypeBuy: //买涨 dealPrice = newPrice.Sub(difference).String() // 平仓价格 case flags.TradeTypeSell: // 买跌 dealPrice = newPrice.Add(difference).String() // 平仓价格 } } } if checkBool { deleteCache = true order.Status = flags.Close } if deleteCache { // 根据订单ID-删除持仓列表缓存订单 if err := Reds.HDel(context.Background(), setting.MarketShareHkdPosition, order.OrderId).Err(); err != nil { applogger.Error("%v ShareHkdConcurrentComputingPosition.Position.HDel:%v", common.ErrShareHkd, err) } } wg.Done() return share.ShareHkdTallyCache{ UserId: order.UserId, // 用户Id OrderId: order.OrderId, // 订单Id Symbol: order.Symbol, // 下单交易对 ClosingPrice: dealPrice, // 平仓价格 Status: order.Status, // 订单状态 Order: order.Order, // 下单信息 ClosingTime: order.ClosingTime, // 平仓时间 } } // ShareHkdMqClosingBusiness // // @Description: TODO: 处理港股平仓消息队列 // @param ctx // @param uo func ShareHkdMqClosingBusiness(ctx context.Context, uo *Data) { go func() { uo.mqConsumer.Position.ConsumerNotice() }() for { select { case message, ok := <-consumer.ConsumerPositionMq.ShareHkdMq: if !ok { time.Sleep(5 * time.Second) applogger.Error("%v ShareHkdMqClosingBusiness.ShareHkdMq err....", common.ErrShareHkd) continue } applogger.Info("接收到平仓信息:%v", string(message)) // 持仓订单缓存数据序列化解析 var resultMsg share.ShareHkdTallyCache if err := json.Unmarshal(message, &resultMsg); err != nil { applogger.Error("%v ShareHkdMqClosingBusiness.Unmarshal:%v", common.ErrShareHkd, err) time.Sleep(5 * time.Second) continue } // 持仓平仓 if err := ShareHkdLiquidationPosition(ctx, &resultMsg); err != nil { applogger.Error("%v ShareHkdMqClosingBusiness.ShareHkdLiquidationPosition:%v", common.ErrShareHkd, err) continue } default: // TODO: 是否处理空队列缓存的情况 time.Sleep(2 * time.Second) } } } // ShareHkdLiquidationPosition // // @Description: 港股持仓-->平仓业务处理 // @param ctx // @param order // @return error func ShareHkdLiquidationPosition(ctx context.Context, order *share.ShareHkdTallyCache) error { // 1、平仓操作 err := StockHkdClosingOrder(ctx, Msql, order.OrderId, order.ClosingPrice, order.Order) if err != nil { applogger.Error("%v ShareHkdLiquidationPosition.StockIdnClosingOrder:%v", common.ErrShareHkd, err) return err } // 2、平仓更新用户订阅订单状态 userSubKey := virtual.OrderIdListKey(setting.ShareHkdSubscribe, order.UserId) if err = UpdateShareHkdSubscribeHashStatusByOrderId(order.OrderId, userSubKey, flags.Close, order.OpenPrice); err != nil { applogger.Error("%v ShareHkdLiquidationPosition.Position.ShareHkdSubscribe:%v", common.ErrShareHkd, err) return err } // 3、平仓更新管理员订阅订单状态 if err = UpdateShareHkdSubscribeHashStatusByOrderId(order.OrderId, setting.AdminShareHkdSubscribe, flags.Close, order.OpenPrice); err != nil { applogger.Error("%v ShareHkdLiquidationPosition.Position.AdminShareHkdSubscribe:%v", common.ErrShareHkd, err) return err } return nil } // StockHkdVoteStopType // // @Description: 港股判定取值止损止盈 // @param order // @return bool // @return decimal.Decimal // @return decimal.Decimal // @return error func StockHkdVoteStopType(order structure.ShareOrder) (bool, decimal.Decimal, decimal.Decimal, error) { var checkBool bool var stopWinPrice, stopLossPrice decimal.Decimal switch order.StopType { case flags.StopTypeNone: // 暂无设置止盈止损 checkBool = false case flags.StopTypeSet: // 设置止盈止损 checkBool = true stopWinPrice, _ = decimal.NewFromString(order.StopWinPrice) stopLossPrice, _ = decimal.NewFromString(order.StopLossPrice) default: return checkBool, stopWinPrice, stopLossPrice, flags.ErrContractThirteen } return checkBool, stopWinPrice, stopLossPrice, nil } // GetShareHkdPrice // // @Description: 查询港股股票价格 // @param symbol // @return decimal.Decimal // @return decimal.Decimal // @return decimal.Decimal // @return error func GetShareHkdPrice(symbol string) (decimal.Decimal, error) { var err error var priceByte []byte var newPrice decimal.Decimal key := publicData.SymbolCache(flags.Hkd, symbol, flags.TradeTypePrice) priceByte, err = memory.GetShareHkdCache(key) if err != nil { return decimal.Decimal{}, err } newPrice, err = decimal.NewFromString(string(priceByte)) if err != nil { return decimal.Decimal{}, err } applogger.Info("shareHkdCode:%v,topIc:%v,shareHkdPrice:%v", symbol, key, newPrice) return newPrice, nil }