You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1123 lines
37 KiB

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
}