155 lines
3.4 KiB
Go
155 lines
3.4 KiB
Go
package logic
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"big-qmt/go-client/sdk"
|
|
)
|
|
|
|
var (
|
|
STOCK_DIRECTION = 48
|
|
STOCK_SIDE_BUY = 48
|
|
STOCK_SIDE_SELL = 49
|
|
OffsetFlag = map[string]string{"23": "BUY", "24": "SELL", "48": "BUY", "49": "SELL"}
|
|
OrderTimeout = 5 * time.Minute
|
|
|
|
OrderBook *Books
|
|
)
|
|
|
|
type OrderItem struct {
|
|
ID string
|
|
Code string
|
|
Side string
|
|
Remark string
|
|
Status string
|
|
CreatedAt time.Time
|
|
Volume int
|
|
}
|
|
|
|
type Books struct {
|
|
mu sync.Mutex
|
|
Data map[string]*OrderItem
|
|
Index []string
|
|
}
|
|
|
|
func NewOrderBook() {
|
|
OrderBook = &Books{Data: make(map[string]*OrderItem), Index: make([]string, 0)}
|
|
}
|
|
|
|
func NewOrderID(leg string) string {
|
|
var random [6]byte
|
|
_, _ = rand.Read(random[:])
|
|
tag := fmt.Sprintf("zt-%s-%s", leg, hex.EncodeToString(random[:]))
|
|
if len(tag) > 24 {
|
|
return tag[:24]
|
|
}
|
|
return tag
|
|
}
|
|
|
|
func (o *Books) IsLock(side, code string) bool {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
|
|
keyStr := fmt.Sprintf("%s-%s", side, code)
|
|
return slices.Contains(o.Index, keyStr)
|
|
}
|
|
|
|
func (o *Books) Refresh(client *sdk.Client) error {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
raw, err := client.TradeDetailData(context.Background(), "order")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var idx []string
|
|
orders := make(map[string]*OrderItem)
|
|
for _, row := range raw {
|
|
keyStr, item := parseOrder(row)
|
|
orders[keyStr] = item
|
|
idx = append(idx, keyStr)
|
|
}
|
|
o.Data = orders
|
|
o.Index = idx
|
|
return nil
|
|
}
|
|
|
|
func (o *Books) CancelExpired(client *sdk.Client) error {
|
|
ctx := context.Background()
|
|
err := o.Refresh(client)
|
|
if err != nil {
|
|
return fmt.Errorf("[委托] 查询失败: %v", err)
|
|
}
|
|
for _, order := range o.Data {
|
|
if order.CreatedAt.IsZero() || time.Since(order.CreatedAt) <= OrderTimeout {
|
|
continue
|
|
}
|
|
if order.ID != "" {
|
|
rs, err := client.CanCancelOrder(ctx, order.ID)
|
|
if err != nil {
|
|
logf("ERROR", "[委托] 撤销失败:%v", err)
|
|
continue
|
|
} else {
|
|
logf("INFO", "[委托] 撤销成功:%v", rs)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (o *Books) Place(client *sdk.Client, op int, code string, volume int, sn string) bool {
|
|
if _, err := client.PassorderLatestTagged(context.Background(), op, code, volume, sn); err != nil {
|
|
logf("ERROR", "[委托] %s 下单失败: %v", code, err)
|
|
return false
|
|
}
|
|
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
|
|
keyStr := fmt.Sprintf("%s-%s", OffsetFlag[strconv.Itoa(op)], code)
|
|
o.Index = append(o.Index, keyStr)
|
|
|
|
logf("INFO", "[委托] 下单已提交 %d %s %d股", op, code, volume)
|
|
return true
|
|
}
|
|
|
|
func parseOrder(row map[string]string) (string, *OrderItem) {
|
|
left, _ := strconv.Atoi(row["m_nVolumeTotal"])
|
|
traded, _ := strconv.Atoi(row["m_nVolumeTraded"])
|
|
volume := left + traded
|
|
|
|
item := &OrderItem{
|
|
ID: row["m_strOrderSysID"],
|
|
Code: row["m_strInstrumentID"],
|
|
Side: OffsetFlag[row["m_nOffsetFlag"]],
|
|
Remark: row["m_strRemark"],
|
|
Status: row["m_nOrderStatus"],
|
|
Volume: volume,
|
|
CreatedAt: time.Unix(parseTimestamp(row), 0),
|
|
}
|
|
keyStr := fmt.Sprintf("%s-%s", item.Side, item.Code)
|
|
return keyStr, item
|
|
}
|
|
|
|
func parseTimestamp(row map[string]string) int64 {
|
|
ts, _ := strconv.ParseInt(row["m_nOrderTime"], 10, 64)
|
|
if ts > 1e11 {
|
|
return ts / 1000
|
|
}
|
|
if ts > 0 {
|
|
return ts
|
|
}
|
|
date := row["m_strInsertDate"]
|
|
clock := strings.ReplaceAll(row["m_strInsertTime"], ":", "")
|
|
clock = strings.Repeat("0", max(0, 6-len(clock))) + clock
|
|
t, _ := time.ParseInLocation("20060102150405", date+clock, time.Local)
|
|
return t.Unix()
|
|
}
|