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/optionData" "matchmaking-system/internal/data/socket/publicData" "matchmaking-system/internal/data/tradedeal/option" "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" ) /** 期权-交易计算相关公式 一、手续费(Total Fee) 1> 固定费用:Total Fee = Total Fee Fix Value(固定费用) 2> 按比例结算:Total Fee = Total Fee Ratio(总费用比例) * Option Filled Price(开仓价格) * Quantity(订单数量) 3> 按张结算:Total Fee = Total Fee Per Contract(每份费用) * Quantity(订单数量) 三、浮动盈亏(P/L) 1>buy call & buy put 1> bid买一价为0, P/L显示为 - 2> bid买一价大于0, P/L =(bid买一价 - Cost Price)*Contracts Quantity 2>sell call & sell put 1> ask卖一价为0, P/L显示为 - 2> ask卖一价大于0, P/L =(Cost Price - ASK卖一价)*Contracts Quantity 四、保证金(Margin Ratio) 1> sell call 和 sell put:Margin =(Stock Price(行权价) * Option Multiplier(期权乘数)) * Margin Ratio(保证金比率) * Quantity 2> buy call 和 buy put:Margin = Open Filled Price(开仓价格) * Quantity(订单数量) 五、结算价值(Settlement Value) 1> Margin + P/L */ // OptionInrMessage // @Description: type OptionInrMessage struct { Price string `json:"price"` Bid string `json:"bid"` Ask string `json:"ask"` Code string `json:"code"` BeforeClose string `json:"before_close"` YesterdayClose string `json:"yesterday_close"` CloseDate string `json:"option_date"` } // OptionInrSymbol // @Description: type OptionInrSymbol struct { OpiMap chan []byte // 订单交易对 -- 用户下单通知订阅 OpiSetMap chan []byte // 订单交易对 -- 用户盘前|盘后行情订阅 OpiMapSymbol sync.Map // 订阅交易簿 -- 过滤重复 OpiSetMapSymbol sync.Map // 订阅交易簿 -- 过滤重复 } // InitCacheSymbolOptionInr // // @Description: // @param ctx // @param uo func InitCacheSymbolOptionInr(ctx context.Context, uo *Data) { for { thaList, err := GetBotStockOptionInrListByStockId(ctx, uo) if err != nil { return } for _, value := range thaList { // Write to the redis list hot cache for initializing subscriptions when the program is pulled up checkBool, err := Reds.HExists(context.Background(), setting.MarketOptionInr, value).Result() if err != nil { applogger.Error("%v InitCacheSymbolOptionInr.MarketOptionInr.HExists:%v", common.ErrOptionInr, err) time.Sleep(5 * time.Second) continue } if !checkBool { if err := Reds.HSet(context.Background(), setting.MarketOptionInr, value, value).Err(); err != nil { applogger.Error("%v InitCacheSymbolOptionInr.MarketOptionInr.HSet:%v", common.ErrOptionInr, err) time.Sleep(5 * time.Second) continue } } if !flags.CheckSetting { applogger.Info("初始化印度期权股票代码:%v", value) } } time.Sleep(10 * time.Second) } } // InitSubscribeOptionInr // // @Description: 状态机加载行情 - 订单表预加载 // @param ctx // @param uo // @param shareTha func InitSubscribeOptionInr(OptionInr *OptionInrSymbol) { for { listSpots, err := LoadLRangeList(setting.AdminOptionInrSubscribe) if err != nil { applogger.Error("%v InitSubscribeOptionInr.LoadLRangeList:%v", common.ErrOptionInr, err) return } for _, value := range listSpots { var entrust option.OptionInrTallyCache if err = json.Unmarshal([]byte(value), &entrust); err != nil { applogger.Error("%v InitSubscribeOptionInr.Unmarshal:%v", common.ErrOptionInr, err) continue } _, ok := OptionInr.OpiMapSymbol.Load(entrust.Order.StockCode) if ok { continue } OptionInr.OpiMap <- []byte(entrust.Order.StockCode) } time.Sleep(20 * time.Second) } } // SubscriptionDataInformation // // @Description: 循环处理数据 // @param ctx func SubscriptionDataInformation(ctx context.Context) { for { optionInr.OpiMapSymbol.Range(func(key, value interface{}) bool { topIc, ok := key.(string) if ok { symbolStr := utils.ExtractSubstringBeforeFirstDigit(topIc) _ = OptionInrMessageSet(symbolStr, topIc) } return true }) time.Sleep(600 * time.Millisecond) } } // SubscribeOptionInr // // @Description: 期权-印度股订阅 // @param ctx // @param uo // @param shareIdn func SubscribeOptionInr(ctx context.Context, uo *Data, optionInr *OptionInrSymbol) { for { select { case symbol, _ := <-optionInr.OpiMap: // 期权标识:BEL20240425216000PE topIc := string(symbol) // 构造股票(BEL) symbolStr := utils.ExtractSubstringBeforeFirstDigit(topIc) // 处理重复订阅的情况 _, ok := optionInr.OpiMapSymbol.Load(topIc) if ok { time.Sleep(5 * time.Second) continue } // 将订阅的主题写入Redis的hot-cache列表 checkBool, err := Reds.HExists(context.Background(), setting.MarketOptionInr, symbolStr).Result() if err != nil { continue } if !checkBool { if err := Reds.HSet(context.Background(), setting.MarketOptionInr, symbolStr, symbolStr).Err(); err != nil { applogger.Error("%v SubscribeOptionInr.MarketOptionInr.HSet:%v", common.ErrOptionInr, err) time.Sleep(5 * time.Second) continue } } // 获取设置价格 if err := OptionInrMessageSet(symbolStr, topIc); err != nil { applogger.Error("OptionInrMessageSet err:%v", topIc) continue } // 将订阅的主题存入map中,用于判断是否重复订阅 optionInr.OpiMapSymbol.Store(topIc, topIc) default: Reds.Ping(ctx) // 如果没有消息,发送 PING 命令以维护连接 time.Sleep(10 * time.Second) } } } // OptionInrMessageSet // // @Description: 设置股票交易价格|买一卖一价格|闭盘价格 // @param topIc // @return error func OptionInrMessageSet(symbolStr, topIc string) error { // 查询订单 keyCache := fmt.Sprintf("%v%v", flags.CountryOptionInr, symbolStr) msg, err := Reds.HGet(context.Background(), keyCache, topIc).Result() if err != nil { return err } // 交易数据解析 var subMsg OptionInrMessage if err = json.Unmarshal([]byte(msg), &subMsg); err != nil { applogger.Error("%v SubscribeOptionInr.Unmarshal:%v", common.ErrOptionInr, err) close(optionInr.OpiMap) return err } // 处理订阅实时行情数据(订阅Key|交易价格|买一价|卖一价) SubscribeOptionInrPrice(subMsg, topIc) return nil } // SubscribeOptionInrPrice // // @Description: 期权-订阅获取印度股实时价格或者[盘前|盘后]设置价格 // @param setPrice // @param symbolStr // @param check func SubscribeOptionInrPrice(optionInr OptionInrMessage, topIc string) { // 交易类型: 期权最新价格-0,期权买一价-1,期权卖一价-2 // 缓存期权交易价格 keyPrice := publicData.SymbolCache(flags.Opi, topIc, flags.OptionPrice) if err := memory.OptionInrCache.Set(keyPrice, []byte(optionInr.Price)); err != nil { applogger.Error("%v SubscribeOptionInrPrice.OptionInrCache.Set.price:%v", common.ErrOptionInr, err) } // 缓存期权买一价格 keyBid := publicData.SymbolCache(flags.Opi, topIc, flags.OptionBid) if err := memory.OptionInrBid.Set(keyBid, []byte(optionInr.Bid)); err != nil { applogger.Error("%v SubscribeOptionInrPrice.OptionInrBid.Set.price:%v", common.ErrOptionInr, err) } // 缓存期权卖一价格 keyAsk := publicData.SymbolCache(flags.Opi, topIc, flags.OptionAsk) if err := memory.OptionInrAsk.Set(keyAsk, []byte(optionInr.Ask)); err != nil { applogger.Error("%v SubscribeOptionInrPrice.OptionInrAsk.Set.price:%v", common.ErrOptionInr, err) } // 设置闭盘价格 var price decimal.Decimal closeStr := utils.ReplaceStr(optionInr.Price) == "" yesterdayStr := utils.ReplaceStr(optionInr.YesterdayClose) == "" beforeStr := utils.ReplaceStr(optionInr.BeforeClose) == "" if closeStr { optionInr.Price = decimal.Zero.String() } if yesterdayStr { optionInr.YesterdayClose = decimal.Zero.String() } if beforeStr { optionInr.BeforeClose = decimal.Zero.String() } closePrice := decimal.RequireFromString(optionInr.Price) yesterdayClosePrice := decimal.RequireFromString(optionInr.YesterdayClose) beforeClose := decimal.RequireFromString(optionInr.BeforeClose) // 判定如果close为zero则取值yesterdayClosePrice,否取closePrice if closePrice.IsZero() { if !yesterdayClosePrice.IsZero() { price = yesterdayClosePrice } } else { price = closePrice } // 判定如果price为zero则取值beforeClose值,否取price if price.IsZero() { if !beforeClose.IsZero() { price = beforeClose } } // 闭盘价格 closeKey := publicData.SymbolCache(flags.Opi, topIc, flags.TradeTypeClosePrice) if err := memory.OptionInrClosePrice.Set(closeKey, []byte(price.String())); err != nil { applogger.Error("%v OptionInrClosePrice.Set.price:%v", common.ErrOptionInr, err) } if !flags.CheckSetting { applogger.Info("期权股票交易代码:%v,交易价格:%v,买一价:%v,卖一价:%v,闭盘价:%v", topIc, optionInr.Price, optionInr.Bid, optionInr.Ask, price) } } // OrderSubAdminOptionInrSubscribeBySum // // @Description: 期权-持仓订单浮动盈亏计算 // @param ctx // @param uo func OrderSubAdminOptionInrSubscribeBySum(uo *Data) { for { topIc := setting.AdminOptionInrSubscribe hashMap, err := Reds.HGetAll(context.Background(), topIc).Result() if err != nil { applogger.Error("OrderSubAdminOptionInrSubscribeBySum.HGetAll:%v", err) time.Sleep(5 * time.Second) continue } var hashList []option.OptionInrTallyCache for _, value := range hashMap { var msg option.OptionInrTallyCache 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 = Reds.HDel(context.Background(), topIc, msg.OrderId).Err() if err != nil { applogger.Error("OrderSubAdminOptionInrSubscribeBySum.HDel:%v", err) continue } } } applogger.Info("统计印度期权股数据量统计:%v", len(hashList)) // 统计印度股持仓订单浮动盈亏 priceSum := decimal.Zero for _, value := range hashList { orderModel := optionData.OptionInrOrderProcessing(1, value) if orderModel != nil { if len(orderModel.Price) == 0 || len(orderModel.OpenPrice) == 0 { continue } var newPrice, openPrice, orderNumber, subPrice, resultPrice decimal.Decimal newPrice = decimal.RequireFromString(orderModel.Price) // 股票实时价格 openPrice = decimal.RequireFromString(orderModel.OpenPrice) // 订单开仓价格 orderNumber = decimal.RequireFromString(orderModel.OrderNumber) // 订单量 subPrice = OptionResultPrice(value.Order.TradeType, value.Order.TradingType, openPrice, newPrice) // 计算浮动盈亏 resultPrice = subPrice.Mul(orderNumber) // 计算订单浮动总盈亏 priceSum = priceSum.Add(resultPrice) // 累加盈亏统计 } } // 写入持仓订单浮动盈亏缓存 if err = memory.OptionInrFloating.Set(flags.FloatingOpi, []byte(priceSum.String())); err != nil { applogger.Error("统计印度期权股持仓订单浮动盈亏错误:%v", err) time.Sleep(5 * time.Second) continue } applogger.Info("统计印度期权股持仓订单浮动盈亏:%v", priceSum) time.Sleep(3 * time.Second) } } // OptionResultPrice // // @Description: 期权-浮动盈亏 // @param tradeType // @param tradingType // @param openPrice // @param newPrice // @return decimal.Decimal func OptionResultPrice(tradeType, tradingType int64, openPrice, newPrice decimal.Decimal) decimal.Decimal { //浮动盈亏(P/L) //1>buy call & buy put // 1> bid买一价为0, P/L显示为 - // 2> bid买一价大于0, P/L =(bid买一价 - Cost Price)*Contracts Quantity //2>sell call & sell put // 1> ask卖一价为0, P/L显示为 - // 2> ask卖一价大于0, P/L =(Cost Price - ASK卖一价)*Contracts Quantity var subPrice decimal.Decimal switch tradeType { case flags.OptionCalls: // Call switch tradingType { case flags.OptionBuy: // buy - call subPrice = newPrice.Sub(openPrice) case flags.OptionSell: // sell - call subPrice = openPrice.Sub(newPrice) } case flags.OptionPuts: // PUT switch tradingType { case flags.OptionBuy: // buy - put subPrice = newPrice.Sub(openPrice) case flags.OptionSell: // sell - put subPrice = openPrice.Sub(newPrice) } default: subPrice = decimal.Zero } return subPrice } // OptionInrTransactionEntrust // // @Description: 期权-印度股委托订单 // @param ctx func OptionInrTransactionEntrust(ctx context.Context, uo *Data) { for { OptionInrTransactionCalculationEntrust(ctx, uo) time.Sleep(400 * time.Millisecond) } } // OptionInrTransactionCalculationEntrust // // @Description: 期权-印度股挂单缓存队列计算 // @param ctx // @param uo func OptionInrTransactionCalculationEntrust(ctx context.Context, uo *Data) { var wg sync.WaitGroup // 全局变量处理 if CheckGlobalTread(flags.OpiMarket) { time.Sleep(5 * time.Second) return } // 获取印度期权股挂单缓存列表 orderMap, err := Reds.HGetAll(context.Background(), setting.MarketOptionInrEntrust).Result() if err != nil { applogger.Error("%v OptionInrTransactionCalculationEntrust.HGetAll:%v", common.ErrOptionInr, err) return } for _, value := range orderMap { var msg = make(chan option.OptionInrTallyCache, 1) var entrust option.OptionInrTallyCache if err = json.Unmarshal([]byte(value), &entrust); err != nil { applogger.Error("%v OptionInrTransactionCalculationEntrust.Unmarshal:%v", common.ErrOptionInr, err) continue } // 到期时间小于当前时间-永远不给成交(撤单操作) if CheckShareSystemTime(entrust.ClosingTime) { applogger.Warn("%v 订单已超过到期时间不在成交:%v---%v", common.ErrOptionInr, entrust.Symbol, entrust.ClosingTime) _, err = UpdateBotStockOptionInrCancelByOrderId(ctx, uo.mysqlDB, entrust.OrderId) if err != nil { applogger.Warn("%v 订单已超过到期时间不在成交,订单撤单失败:%v---%v", common.ErrOptionInr, entrust.Symbol, entrust.ClosingTime) } continue } // 期权交易价格|买一价|卖一价 var newPrice, bid, ask decimal.Decimal newPrice, bid, ask, err = GetOptionInrPrice(entrust.Order.StockCode) if err != nil { applogger.Warn("%v OptionInrTransactionCalculationEntrust.GetOptionInrPrice:%v---%v", common.ErrOptionInr, entrust.Symbol, err) continue } wg.Add(1) go func(value string) { var resultMsg option.OptionInrTallyCache switch entrust.Order.DealType { case flags.DealTypeLimited: // 限价-挂单 resultMsg = OptionInrConcurrentComputingEntrustLimited(&entrust, newPrice, bid, ask, &wg) case flags.DealTypeMarket: // 市价-挂单(开盘即成交) resultMsg = OptionInrConcurrentComputingEntrustMarket(&entrust, bid, ask, &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 = OptionInrLiquidationEntrust(ctx, &resultMsg); err != nil { applogger.Warn("%v OptionInrTransactionCalculationEntrust.OptionInrLiquidationEntrust:%v", common.ErrOptionInr, err) continue } // 写入持仓缓存列表 var byteStr []byte byteStr, err = json.Marshal(resultMsg) if err != nil { applogger.Error("%v OptionInrTransactionCalculationEntrust.Marshal:%v", common.ErrOptionInr, err) continue } // TODO: 写入持仓消息队列 //if err = uo.mqProducer.Entrust.PushNotice(byteStr); err != nil { // applogger.Error("%v OptionInrTransactionCalculationEntrust.EntrustPushNotice:%v", common.ErrOptionInr, err) // continue //} // 写入持仓队列缓存 if err = Reds.HSet(context.Background(), setting.MarketOptionInrPosition, resultMsg.OrderId, string(byteStr)).Err(); err != nil { continue } } } } wg.Wait() applogger.Info("期权-印度股挂单并发计算执行完毕......") } // OptionInrConcurrentComputingEntrustLimited // // @Description: 期权-印度股挂单(限价|市价)缓存列表业务处理 // @param order // @param newPrice // @param wg // @return option.OptionInrTallyCache func OptionInrConcurrentComputingEntrustLimited(order *option.OptionInrTallyCache, newPrice, bid, ask decimal.Decimal, wg *sync.WaitGroup) option.OptionInrTallyCache { var deleteCache bool var dealPrice string var status = order.Status // 订单状态 var limitPrice = decimal.RequireFromString(order.Order.LimitPrice) // 限价-价格 //限价单成交逻辑 //1>buy-call 和 buy-put 当卖一价(aks) <= 下单价 会成交即为下单价 //2>sell-call 和 sell-put 当买一价(bid) >= 下单价 会成交即为下单价 switch order.Order.TradeType { case flags.OptionCalls: // call switch order.Order.TradingType { case flags.OptionBuy: // buy - call if ask.Cmp(limitPrice) <= 0 { deleteCache = true dealPrice = limitPrice.String() } case flags.OptionSell: // sell - call if bid.Cmp(limitPrice) >= 0 { deleteCache = true dealPrice = limitPrice.String() } } case flags.OptionPuts: // put switch order.Order.TradingType { case flags.OptionBuy: // buy - put if ask.Cmp(limitPrice) <= 0 { deleteCache = true dealPrice = limitPrice.String() } case flags.OptionSell: // sell - put if bid.Cmp(limitPrice) >= 0 { deleteCache = true dealPrice = limitPrice.String() } } } if deleteCache { order.Status = flags.Position if err := Reds.HDel(context.Background(), setting.MarketOptionInrEntrust, order.OrderId).Err(); err != nil { applogger.Error("%v OptionInrConcurrentComputingEntrustLimited.HDel:%v", common.ErrOptionInr, err) order.Status = status } } wg.Done() return option.OptionInrTallyCache{ UserId: order.UserId, // 用户Id OrderId: order.OrderId, // 订单Id Symbol: order.Symbol, // 下单交易对 OpenPrice: dealPrice, // 开仓价格 Status: order.Status, // 订单状态 Order: order.Order, // 下单信息 ClosingTime: order.ClosingTime, // 平仓时间 } } // OptionInrConcurrentComputingEntrustMarket // // @Description: 期权-印度股市价下单进入挂单队列(休市) // @param order // @param newPrice // @param wg // @return option.OptionInrTallyCache func OptionInrConcurrentComputingEntrustMarket(order *option.OptionInrTallyCache, bid, ask decimal.Decimal, wg *sync.WaitGroup) option.OptionInrTallyCache { var deleteCache bool var dealPrice string var status = order.Status // 原始状态 //市价单交易 //1>buy-call 和 buy-put 以卖一价(ask)成交 //2>sell-call 和 sell-put 以买一价(bid)成交 switch order.Order.TradeType { case flags.OptionCalls: // call switch order.Order.TradingType { case flags.OptionBuy: // buy - call if !ask.IsZero() { deleteCache = true dealPrice = ask.String() } case flags.OptionSell: // sell - call if !bid.IsZero() { deleteCache = true dealPrice = bid.String() } } case flags.OptionPuts: // put switch order.Order.TradingType { case flags.OptionBuy: // buy - put if !ask.IsZero() { deleteCache = true dealPrice = ask.String() } case flags.OptionSell: // sell - put if !bid.IsZero() { deleteCache = true dealPrice = bid.String() } } } if deleteCache { order.Status = flags.Position if err := Reds.HDel(context.Background(), setting.MarketOptionInrEntrust, order.OrderId).Err(); err != nil { applogger.Error("%v OptionInrConcurrentComputingEntrustMarket.HDel:%v", common.ErrOptionInr, err) order.Status = status } } wg.Done() return option.OptionInrTallyCache{ UserId: order.UserId, // 用户Id OrderId: order.OrderId, // 订单Id Symbol: order.Symbol, // 下单交易对 OpenPrice: dealPrice, // 开仓价格 Status: order.Status, // 订单状态 Order: order.Order, // 下单信息 ClosingTime: order.ClosingTime, // 平仓时间 } } // OptionInrMqOpenBusiness // // @Description: TODO: 期权-印度股持仓消息队列 // @param ctx // @param uo func OptionInrMqOpenBusiness(ctx context.Context, uo *Data) { go func() { uo.mqConsumer.Entrust.ConsumerNotice() }() for { select { case message, ok := <-consumer.ConsumerEntrustMq.OptionInrMq: if !ok { time.Sleep(5 * time.Second) applogger.Error("%v OptionInrMqOpenBusiness.OptionInrMq err....", common.ErrOptionInr) continue } // 持仓订单缓存数据序列化解析 var resultMsg option.OptionInrTallyCache if err := json.Unmarshal(message, &resultMsg); err != nil { applogger.Error("%v OptionInrMqOpenBusiness.Unmarshal:%v", common.ErrOptionInr, err) time.Sleep(5 * time.Second) continue } // 持仓平仓 if err := OptionInrLiquidationEntrust(ctx, &resultMsg); err != nil { applogger.Error("%v OptionInrMqOpenBusiness.OptionInrLiquidationEntrust:%v", common.ErrOptionInr, err) continue } default: // TODO: 是否处理空队列缓存的情况 applogger.Error("这里没有监控到持仓mq队列消息.........") time.Sleep(2 * time.Second) } } } // OptionInrLiquidationEntrust // // @Description: 期权-印度股挂单-->开仓业务处理 // @param ctx // @param order // @return error func OptionInrLiquidationEntrust(ctx context.Context, order *option.OptionInrTallyCache) error { // 1、开盘 err := OptionInrOpenOrder(ctx, Msql, order.UserId, order.OrderId, order.OpenPrice, order.Order) if err != nil { applogger.Error("%v OptionInrLiquidationEntrust.OptionInrOpenOrder:%v", common.ErrOptionInr, err) return err } // 2、更新用户订阅缓存列表订单状态 updateKey := virtual.OrderIdListKey(setting.OptionInrSubscribe, order.UserId) if err = UpdateOptionInrSubscribeHashStatusByOrderId(order.OrderId, updateKey, flags.Position, order.OpenPrice); err != nil { applogger.Error("%v OptionInrLiquidationEntrust.Entrust.OptionInrSubscribe:%v", common.ErrOptionInr, err) return err } // 3、更新管理员订阅缓存列表订单状态 if err = UpdateOptionInrSubscribeHashStatusByOrderId(order.OrderId, setting.AdminOptionInrSubscribe, flags.Position, order.OpenPrice); err != nil { applogger.Error("%v OptionInrLiquidationEntrust.Entrust.AdminOptionInrSubscribe:%v", common.ErrOptionInr, err) return err } return nil } // OptionInrTransactionPosition // // @Description: 期权-印度股持仓订单 // @param ctx func OptionInrTransactionPosition(ctx context.Context) { for { OptionInrTransactionCalculationPosition(ctx) time.Sleep(400 * time.Millisecond) } } // OptionInrTransactionCalculationPosition // // @Description: 期权-印度股持仓缓存队列计算 // @param ctx func OptionInrTransactionCalculationPosition(ctx context.Context) { var wg sync.WaitGroup // 判定是否交易 if CheckGlobalTread(flags.OpiMarket) { time.Sleep(5 * time.Second) return } // TODO: 获取IPO未支付订单缓存 userIdMap, _ := GetBotUserArrears(ctx) // 持仓缓存列表 orderMap, err := Reds.HGetAll(context.Background(), setting.MarketOptionInrPosition).Result() if err != nil { applogger.Error("%v OptionInrTransactionCalculationPosition.HGetAll:%v", common.ErrOptionInr, err) return } for _, value := range orderMap { var msg = make(chan option.OptionInrTallyCache, 1) var entrust option.OptionInrTallyCache if err = json.Unmarshal([]byte(value), &entrust); err != nil { applogger.Error("%v OptionInrTransactionCalculationPosition.Unmarshal:%v", common.ErrOptionInr, err) continue } // TODO: 判定是否未IPO未支付订单-不能平仓 _, isOk := userIdMap[entrust.UserId] if isOk { continue } // 期权交易价格|买一价|卖一价 var newPrice, bid, ask decimal.Decimal newPrice, bid, ask, err = GetOptionInrPrice(entrust.Order.StockCode) if err != nil { applogger.Warn("%v OptionInrTransactionCalculationPosition.Get:%v--%v", common.ErrOptionInr, entrust.Symbol, err) continue } // 期权到期时间日内停盘前一个小时平仓 checkClosingTime := utils.TimeComparison(entrust.ClosingTime) wg.Add(1) go func(value string) { resultMsg := OptionInrConcurrentComputingPosition(&entrust, newPrice, bid, ask, &wg, checkClosingTime) 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 OptionInrTransactionCalculationPosition.Marshal:%v", common.ErrOptionInr, err) // continue //} //if err = uo.mqProducer.Position.PushNotice(byteStr); err != nil { // applogger.Error("%v OptionInrTransactionCalculationPosition.DealPublish:%v", common.ErrOptionInr, err) // continue //} if err = OptionInrLiquidationPosition(ctx, &resultMsg); err != nil { applogger.Error("%v OptionInrTransactionCalculationPosition.OptionInrLiquidationPosition:%v", common.ErrOptionInr, err) continue } } } } wg.Wait() applogger.Info("期权-印度股持仓并发计算执行完毕......") } // OptionInrConcurrentComputingPosition // // @Description: 期权-印度股持仓缓存列表业务处理 // @param order // @param newPrice // @param wg // @param checkFlattening // @return option.OptionInrTallyCache func OptionInrConcurrentComputingPosition(order *option.OptionInrTallyCache, newPrice, bid, ask decimal.Decimal, wg *sync.WaitGroup, checkClosingTime bool) option.OptionInrTallyCache { var deleteCache bool var openPrice = decimal.RequireFromString(order.OpenPrice) // 限价 var orderNumber = decimal.RequireFromString(order.Order.OrderNumber) // 仓位 // 下单判定设置(false无设置|true止盈止损) _, stopWinPrice, stopLossPrice, _ := OptionInrVoteStopType(order.Order) // 平仓 //1>buy-call 和 buy-put 平仓价格以买一价(bid)成交 //2>sell-call 和 sell-put 平仓价格以卖一价(ask)成交 // 期权:止盈止损 //一、call-buy 和 put-buy //1>止盈:买一价 >= 止盈价 //2>止损:卖一价 <= 止损价 //二、call-sell 和 put-sell //1>止盈:卖一价 <= 止盈价 //2>止损:买一价 >= 止损价 dealPrice, sellShort, checkBool := ClosePriceStopWinOrStopLossPrice(order.Order.TradingType, newPrice, stopWinPrice, stopLossPrice, bid, ask, openPrice, orderNumber) /* 强行平仓(做多|做空) 1、做多:(最新成交价-开仓成交价)*仓位 2、做空:(开仓成交价-最新成交价)*仓位 3、当浮动盈亏 >= 保证金的70%订单强行平仓 */ if sellShort.IsNegative() { //保证金(Margin Ratio) //1> buy call 和 buy put:Margin = Open Filled Price(当前价格) * Quantity(订单数量) //2> sell call 和 sell put:Margin = (Stock Price(行权价) * Option Multiplier(期权乘数)) * Margin Ratio(保证金比率) * Quantity(订单数量) var orderAmount, strikePrice, multiplier, ratio decimal.Decimal ratio = decimal.RequireFromString(order.Order.Ratio) multiplier = decimal.RequireFromString(order.Order.Multiplier) strikePrice = decimal.RequireFromString(order.Order.StrikePrice) switch order.Order.TradeType { case flags.OptionCalls: // CallS switch order.Order.TradingType { case flags.OptionBuy: // buy orderAmount = openPrice.Mul(orderNumber) case flags.OptionSell: // sell orderAmount = strikePrice.Mul(multiplier).Mul(ratio).Mul(orderNumber) } case flags.OptionPuts: // PUTS switch order.Order.TradingType { case flags.OptionBuy: // buy orderAmount = openPrice.Mul(orderNumber) case flags.OptionSell: // sell orderAmount = strikePrice.Mul(multiplier).Mul(ratio).Mul(orderNumber) } default: } 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 dealPrice = newPrice // 平仓价格 } } // 符合: 1、强平条件 2、到达到期时间 if checkBool || checkClosingTime { deleteCache = true order.Status = flags.Close } // 清理持仓缓存订单 if deleteCache { if err := Reds.HDel(context.Background(), setting.MarketOptionInrPosition, order.OrderId).Err(); err != nil { applogger.Error("%v OptionInrConcurrentComputingPosition.Position.HDel:%v", common.ErrOptionInr, err) } } wg.Done() return option.OptionInrTallyCache{ UserId: order.UserId, // 用户Id OrderId: order.OrderId, // 订单Id Symbol: order.Symbol, // 下单交易对 ClosingPrice: dealPrice.String(), // 平仓价格 Status: order.Status, // 订单状态 Order: order.Order, // 下单信息 ClosingTime: order.ClosingTime, // 平仓时间 } } // ClosePriceStopWinOrStopLossPrice // // @Description: 持仓平仓-止盈止损设置判定 // @param tradingType // @param newPrice // @param stopWinPrice // @param stopLossPrice // @param bid // @param ask // @param openPrice // @param orderNumber // @return decimal.Decimal // @return decimal.Decimal // @return bool func ClosePriceStopWinOrStopLossPrice(tradingType int64, newPrice, stopWinPrice, stopLossPrice, bid, ask, openPrice, orderNumber decimal.Decimal) (decimal.Decimal, decimal.Decimal, bool) { var checkBool bool var dealPrice, sellShort decimal.Decimal switch tradingType { case flags.OptionBuy: // call - buy if !stopWinPrice.IsZero() { if bid.Cmp(stopWinPrice) >= 0 { checkBool = true dealPrice = newPrice } } if !stopLossPrice.IsZero() { if ask.Cmp(stopLossPrice) <= 0 { checkBool = true dealPrice = newPrice } } if !newPrice.IsZero() { if !checkBool { // 强行平仓(做多) sellShort = newPrice.Sub(openPrice).Mul(orderNumber) } } case flags.OptionSell: // call -sell if !stopWinPrice.IsZero() { if ask.Cmp(stopWinPrice) <= 0 { checkBool = true dealPrice = newPrice } } if !stopLossPrice.IsZero() { if bid.Cmp(stopLossPrice) >= 0 { checkBool = true dealPrice = newPrice } } if !newPrice.IsZero() { if !checkBool { // 强行平仓(做空) sellShort = openPrice.Sub(newPrice).Mul(orderNumber) } } } return dealPrice, sellShort, checkBool } // OptionInrMqClosingBusiness // // @Description: TODO: 期权-印度股平仓消息队列 // @param ctx // @param uo func OptionInrMqClosingBusiness(ctx context.Context, uo *Data) { go func() { uo.mqConsumer.Position.ConsumerNotice() }() for { select { case message, ok := <-consumer.ConsumerPositionMq.OptionInrMq: if !ok { time.Sleep(5 * time.Second) applogger.Error("%v OptionInrMqClosingBusiness.OptionInrMq err....", common.ErrOptionInr) continue } applogger.Info("接收到平仓信息:%v", string(message)) // 持仓订单缓存数据序列化解析 var resultMsg option.OptionInrTallyCache if err := json.Unmarshal(message, &resultMsg); err != nil { applogger.Error("%v OptionInrMqClosingBusiness.Unmarshal:%v", common.ErrOptionInr, err) time.Sleep(5 * time.Second) continue } // 持仓平仓 if err := OptionInrLiquidationPosition(ctx, &resultMsg); err != nil { applogger.Error("%v OptionInrMqClosingBusiness.OptionInrLiquidationPosition:%v", common.ErrOptionInr, err) continue } default: // TODO: 是否处理空队列缓存的情况 time.Sleep(2 * time.Second) } } } // OptionInrLiquidationPosition // // @Description: 期权-印度股持仓-->平仓业务处理 // @param ctx // @param order // @return error func OptionInrLiquidationPosition(ctx context.Context, order *option.OptionInrTallyCache) error { // 1、平仓操作 err := OptionInrClosingOrder(ctx, Msql, order.OrderId, order.ClosingPrice, order.Order) if err != nil { applogger.Error("%v OptionInrLiquidationPosition.OptionInrClosingOrder:%v", common.ErrOptionInr, err) return err } // 2、平仓更新用户订阅订单状态 userSubKey := virtual.OrderIdListKey(setting.OptionInrSubscribe, order.UserId) if err = UpdateOptionInrSubscribeHashStatusByOrderId(order.OrderId, userSubKey, flags.Close, flags.SetNull); err != nil { applogger.Error("%v OptionInrLiquidationPosition.Position.OptionInrSubscribe:%v", common.ErrOptionInr, err) return err } // 3、平仓更新管理员订阅订单状态 if err = UpdateOptionInrSubscribeHashStatusByOrderId(order.OrderId, setting.AdminOptionInrSubscribe, flags.Close, flags.SetNull); err != nil { applogger.Error("%v OptionInrLiquidationPosition.Position.AdminOptionInrSubscribe:%v", common.ErrOptionInr, err) return err } return nil } // OptionInrVoteStopType // // @Description: 期权-印度股交易下单判定 // @param order // @return bool // @return decimal.Decimal // @return decimal.Decimal // @return error func OptionInrVoteStopType(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 if len(order.StopWinPrice) != 0 { stopWinPrice = decimal.RequireFromString(order.StopWinPrice) } if len(order.StopLossPrice) != 0 { stopLossPrice = decimal.RequireFromString(order.StopLossPrice) } default: return checkBool, stopWinPrice, stopLossPrice, flags.ErrContractThirteen } return checkBool, stopWinPrice, stopLossPrice, nil } // GetOptionInrPrice // // @Description: 期权-印度股股票价格 // @param symbol // @return decimal.Decimal // @return decimal.Decimal // @return decimal.Decimal // @return error func GetOptionInrPrice(stockCode string) (decimal.Decimal, decimal.Decimal, decimal.Decimal, error) { var err error var priceByte, priceBid, priceAsk []byte var newPrice, bidPrice, askPrice decimal.Decimal // 交易价格 priceKey := publicData.SymbolCache(flags.Opi, stockCode, flags.OptionPrice) priceByte, err = memory.GetOptionInrCache(priceKey) if err != nil { return decimal.Decimal{}, decimal.Decimal{}, decimal.Decimal{}, err } newPrice, _ = decimal.NewFromString(string(priceByte)) // 买一价格 bidKey := publicData.SymbolCache(flags.Opi, stockCode, flags.OptionBid) priceBid, err = memory.GetOptionInrBid(bidKey) if err != nil { return decimal.Decimal{}, decimal.Decimal{}, decimal.Decimal{}, err } bidPrice, _ = decimal.NewFromString(string(priceBid)) // 卖一价格 askKey := publicData.SymbolCache(flags.Opi, stockCode, flags.OptionAsk) priceAsk, err = memory.GetOptionInrAsk(askKey) if err != nil { return decimal.Decimal{}, decimal.Decimal{}, decimal.Decimal{}, err } askPrice, _ = decimal.NewFromString(string(priceAsk)) applogger.Info("optionInrCode:%v,optionInrPrice:%v,optionInrBid:%v,optionInrAsk:%v", stockCode, newPrice, bidPrice, askPrice) return newPrice, bidPrice, askPrice, nil } // GetOptionInrStrike // // @Description: 期权-印度获取股票行权价 // @param symbol // @param Expiry // @return decimal.Decimal // @return error func GetOptionInrStrike(symbol, Expiry string) (decimal.Decimal, error) { var err error var priceByte []byte key := publicData.SymbolCache(flags.OpiStk, symbol, flags.TradeTypePrice) priceByte, err = memory.GetOptionInrStrike(key) if err != nil { return decimal.Decimal{}, err } var strikePrice map[string]decimal.Decimal if err = json.Unmarshal(priceByte, &strikePrice); err != nil { return decimal.Decimal{}, err } var strike decimal.Decimal for day, value := range strikePrice { if day == Expiry { strike = value } } applogger.Info("optionInrCode:%v,topIc:%v,optionInrStrikePrice:%v", symbol, key, strikePrice) return strike, nil }